NexusPi Git Node
flash.py copilot/add-rtnode-configuration-options (9529fa8a) Text, 66.57 KB
T8b949e#!/usr/bin/env python3
T8b949e"""
RTNode-HeltecV4 Flash Utility
Flash the RTNode-HeltecV4 transport node firmware to a Heltec WiFi LoRa 32 V3 or V4.
No PlatformIO required — just Python 3 and a USB cable.
By default, downloads the latest firmware from GitHub Releases (if newer than
the local cache) and flashes the app partition only, preserving bootloader,
partition table, NVS, and EEPROM settings. For reproducible flashing, the
script prefers the bundled esptool in Release/ over any host-installed copy.
Usage:
# Update firmware — V4 (default)
python flash.py
# Legacy alias for an app-only update flow
python flash.py --update
# Use a host-installed esptool instead of the bundled copy
python flash.py --use-system-esptool
# Update firmware — V3
python flash.py --board v3
# Flash a specific release version
python flash.py --release v1.0.12
# Full flash with merged binary (overwrites everything)
python flash.py --full
# Flash a specific file (auto-detects merged vs app-only)
python flash.py --file firmware.bin
# Specify serial port manually
python flash.py --port /dev/ttyACM0
# Skip online check — use cached/local firmware only
python flash.py --offline
# Just build the merged binary (for GitHub Releases)
python flash.py --merge-only
"""
Tff7b72import T7ee787argparse
Tff7b72import T7ee787glob
Tff7b72import T7ee787hashlib
Tff7b72import T7ee787os
Tff7b72import T7ee787platform
Tff7b72import T7ee787shutil
Tff7b72import T7ee787subprocess
Tff7b72import T7ee787sys
Tff7b72import T7ee787time
T8b949e# ── Configuration ──────────────────────────────────────────────────────────────
Te6edf3VERSION Tff7b72= Ta5d6ff"Ta5d6ff1.0.18Ta5d6ff"
Te6edf3CHIP Tff7b72= Ta5d6ff"Ta5d6ffesp32s3Ta5d6ff"
Te6edf3FLASH_MODE Tff7b72= Ta5d6ff"Ta5d6ffqioTa5d6ff" T8b949e# Global default; overridden by board profile
Te6edf3FLASH_FREQ Tff7b72= Ta5d6ff"Ta5d6ff80mTa5d6ff"
Te6edf3GITHUB_REPO Tff7b72= Ta5d6ff"Ta5d6ffjrl290/RTNode-HeltecV4Ta5d6ff"
T8b949e# Runtime state (set automatically during main())
Te6edf3_flash_mode_override Tff7b72= Tff7b72None T8b949e# CLI --flash-mode sets this; otherwise board profile wins
Te6edf3_detected_flash_size Tff7b72= Tff7b72None T8b949e# Actual flash size read from device; overrides board profile
Te6edf3_esptool_write_verify_support Tff7b72= Tb4b4b4{Tb4b4b4}
T8b949e# Flash addresses for ESP32-S3 Arduino framework
Te6edf3BOOTLOADER_ADDR Tff7b72= T79c0ff0x0000
Te6edf3PARTITIONS_ADDR Tff7b72= T79c0ff0x8000
Te6edf3BOOT_APP0_ADDR Tff7b72= T79c0ff0xe000
Te6edf3APP_ADDR Tff7b72= T79c0ff0x10000
T8b949e# ── Board profiles ─────────────────────────────────────────────────────────────
T8b949e# Each board defines its PIO env, flash size, baud rate, firmware binary name,
T8b949e# and merged binary name.
T8b949e# Single archive name released on GitHub — contains every board/flash-size variant.
Te6edf3FIRMWARE_ARCHIVE Tff7b72= Ta5d6ff"Ta5d6ffrtnode_firmware.zipTa5d6ff"
T8b949e# Conservative default when flash size can't be detected: 8MB firmware runs on
T8b949e# any device (8MB or larger); a 16MB image on an 8MB device crashes at boot.
Te6edf3DEFAULT_FLASH_SIZE Tff7b72= Ta5d6ff"Ta5d6ff8MBTa5d6ff"
Te6edf3BOARD_PROFILES Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffv4Ta5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffHeltec WiFi LoRa 32 V4Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffchipTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffESP32-S3Ta5d6ff"Tb4b4b4, T8b949e# matches esptool "Chip is ESP32-S3 ..."
Ta5d6ff"Ta5d6ffbaud_rateTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ff921600Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffflash_modeTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffqioTa5d6ff"Tb4b4b4, T8b949e# ESP32-S3 PSRAM requires QIO — DIO disables PSRAM
Ta5d6ff"Ta5d6ffflash_variantsTa5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ff16MBTa5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ffpio_envTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffrtnode_heltec_v4Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffbuild_dirTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ff.pio/build/rtnode_heltec_v4Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fffirmware_binTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffrtnode_heltec_v4.binTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffmerged_binTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffrtnode_heltec_v4_merged.binTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffv3Ta5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffHeltec WiFi LoRa 32 V3Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffchipTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffESP32Ta5d6ff"Tb4b4b4, T8b949e# matches esptool "Chip is ESP32 ..."
Ta5d6ff"Ta5d6ffbaud_rateTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ff460800Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffflash_modeTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffdioTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffflash_variantsTa5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ff8MBTa5d6ff"Tb4b4b4: Tb4b4b4{
Ta5d6ff"Ta5d6ffpio_envTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffrtnode_heltec_v3Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffbuild_dirTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ff.pio/build/rtnode_heltec_v3Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fffirmware_binTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffrtnode_heltec_v3.binTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffmerged_binTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffrtnode_heltec_v3_merged.binTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4}
Te6edf3DEFAULT_BOARD Tff7b72= Ta5d6ff"Ta5d6ffv4Ta5d6ff"
T8b949e# Active board profile (set in main() from --board arg)
Te6edf3_board Tff7b72= Tff7b72None
Tff7b72def Td2a8ffboard_profileTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3BOARD_PROFILESTb4b4b4[Te6edf3_board Tff7b72or Te6edf3DEFAULT_BOARDTb4b4b4]
Tff7b72def Td2a8ffflash_variantTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Return the flash variant dict for the active board and detected flash size.
Falls back to the smallest (safest) available variant when the exact size is
unknown — a smaller-flash firmware runs on any larger device, but not vice versa.
"""
Te6edf3variants Tff7b72= Te6edf3board_profileTb4b4b4(Tb4b4b4)Tb4b4b4[Ta5d6ff"Ta5d6ffflash_variantsTa5d6ff"Tb4b4b4]
Te6edf3size Tff7b72= Te6edf3_detected_flash_size Tff7b72or Te6edf3DEFAULT_FLASH_SIZE
Tff7b72if Te6edf3size Tff7b72in Te6edf3variantsTb4b4b4:
Tff7b72return Te6edf3variantsTb4b4b4[Te6edf3sizeTb4b4b4]
T8b949e# Fallback: smallest available variant
Te6edf3available Tff7b72= Tffa657sortedTb4b4b4(Te6edf3variantsTff7b72.Td2a8ffkeysTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3keyTff7b72=Tff7b72lambda Te6edf3sTb4b4b4: Tffa657intTb4b4b4(Te6edf3sTff7b72.Td2a8ffreplaceTb4b4b4(Ta5d6ff"Ta5d6ffMBTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72return Te6edf3variantsTb4b4b4[Te6edf3availableTb4b4b4[T79c0ff0Tb4b4b4]Tb4b4b4]
Tff7b72def Td2a8ffBUILD_DIRTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3flash_variantTb4b4b4(Tb4b4b4)Tb4b4b4[Ta5d6ff"Ta5d6ffbuild_dirTa5d6ff"Tb4b4b4]
Tff7b72def Td2a8ffMERGED_BINTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Return the path to the pre-merged binary in the PlatformIO build dir."""
Te6edf3merged Tff7b72= Te6edf3flash_variantTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmerged_binTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3mergedTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3BUILD_DIRTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3mergedTb4b4b4)
Tff7b72def Td2a8fffind_local_firmwareTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Look for pre-built firmware binaries adjacent to flash.py.
This is the primary path when the user has extracted the release ZIP.
Prefers the pre-merged binary (full-flash-ready, no merge step needed)
over the app-only binary.
Returns the path if found, or None.
"""
Te6edf3script_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Tff7b72__file__Tb4b4b4)Tb4b4b4)
Te6edf3fv Tff7b72= Te6edf3flash_variantTb4b4b4(Tb4b4b4)
T8b949e# Prefer merged binary — works for both full flash and app-only update
Te6edf3merged Tff7b72= Te6edf3fvTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmerged_binTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3mergedTb4b4b4:
Te6edf3path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3script_dirTb4b4b4, Te6edf3mergedTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3path
T8b949e# Fall back to app-only binary
Te6edf3path Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3script_dirTb4b4b4, Te6edf3fvTb4b4b4[Ta5d6ff"Ta5d6fffirmware_binTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3path
Tff7b72return Tff7b72None
Tff7b72def Td2a8ffBOOTLOADER_BINTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3BUILD_DIRTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffbootloader.binTa5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffPARTITIONS_BINTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3BUILD_DIRTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffpartitions.binTa5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffFIRMWARE_BINTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3BUILD_DIRTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3flash_variantTb4b4b4(Tb4b4b4)Tb4b4b4[Ta5d6ff"Ta5d6fffirmware_binTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72def Td2a8ffFLASH_SIZETb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Return the effective flash size: detected from device, or conservative default."""
Tff7b72return Te6edf3_detected_flash_size Tff7b72or Te6edf3DEFAULT_FLASH_SIZE
Tff7b72def Td2a8ffBAUD_RATETb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3board_profileTb4b4b4(Tb4b4b4)Tb4b4b4[Ta5d6ff"Ta5d6ffbaud_rateTa5d6ff"Tb4b4b4]
Tff7b72def Td2a8ffBOARD_FLASH_MODETb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Return the effective flash mode for the current board.
Priority: CLI override > board profile > global default.
"""
Tff7b72return Te6edf3_flash_mode_override Tff7b72or Te6edf3board_profileTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffflash_modeTa5d6ff"Tb4b4b4, Te6edf3FLASH_MODETb4b4b4)
Tff7b72def Td2a8ffPIO_ENVTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3flash_variantTb4b4b4(Tb4b4b4)Tb4b4b4[Ta5d6ff"Ta5d6ffpio_envTa5d6ff"Tb4b4b4]
T8b949e# ESP32 partition table magic bytes (first two bytes of a partition table entry)
Te6edf3PARTITION_TABLE_MAGIC Tff7b72= Ta5d6ffbTa5d6ff'Tffea00\xaaTffea00\x50Ta5d6ff'
Te6edf3PARTITION_TABLE_SIZE Tff7b72= T79c0ff0xC00 T8b949e# 3072 bytes
Tff7b72def Td2a8ffis_merged_binaryTb4b4b4(Te6edf3firmware_pathTb4b4b4)Tb4b4b4:
T8b949e"""Check whether a firmware file is a merged binary (contains bootloader +
partition table) or an app-only binary.
Returns True for merged, False for app-only.
"""
Tff7b72tryTb4b4b4:
Te6edf3size Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3firmware_pathTb4b4b4)
Tff7b72if Te6edf3size Tff7b72> T79c0ff0x8002Tb4b4b4:
Tff7b72with Tffa657openTb4b4b4(Te6edf3firmware_pathTb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffseekTb4b4b4(T79c0ff0x8000Tb4b4b4)
Tff7b72return Te6edf3fTff7b72.Td2a8ffreadTb4b4b4(T79c0ff2Tb4b4b4) Tff7b72== Te6edf3PARTITION_TABLE_MAGIC
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72return Tff7b72False
Tff7b72def Td2a8ffextract_app_from_mergedTb4b4b4(Te6edf3merged_pathTb4b4b4)Tb4b4b4:
T8b949e"""Extract the app-only portion from a merged binary.
A merged binary starts at 0x0000 and includes bootloader, partition table,
boot_app0, and the app firmware. The region between the partition table
(0x8000-0x8BFF) and boot_app0 (0xE000) contains the NVS partition
(0x9000-0xDFFF) which is filled with 0xFF padding by esptool merge_bin.
Flashing a merged binary therefore wipes all saved settings.
This function extracts bytes from APP_ADDR (0x10000) to the end of the
file, producing an app-only binary that can be flashed at 0x10000 without
touching NVS/EEPROM.
Returns the path to the extracted app-only binary, or None on failure.
"""
Tff7b72tryTb4b4b4:
Te6edf3file_size Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3merged_pathTb4b4b4)
Tff7b72if Te6edf3file_size Tff7b72<Tff7b72= Te6edf3APP_ADDRTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Warning: Merged binary too small (Tffd700{Te6edf3file_sizeTffd700}Ta5d6ff bytes) to contain app data.Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72None
Tff7b72with Tffa657openTb4b4b4(Te6edf3merged_pathTb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffseekTb4b4b4(Te6edf3APP_ADDRTb4b4b4)
Te6edf3app_data Tff7b72= Te6edf3fTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3app_dataTb4b4b4:
Tff7b72return Tff7b72None
Te6edf3baseTb4b4b4, Te6edf3ext Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffsplitextTb4b4b4(Te6edf3merged_pathTb4b4b4)
Te6edf3app_path Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3baseTffd700}Ta5d6ff_appTffd700{Te6edf3extTffd700}Ta5d6ff"
Tff7b72with Tffa657openTb4b4b4(Te6edf3app_pathTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3fTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3app_dataTb4b4b4)
Tff7b72return Te6edf3app_path
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Warning: Could not extract app from merged binary: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72None
Tff7b72def Td2a8ff_find_in_platformio_or_releaseTb4b4b4(Te6edf3build_pathTb4b4b4, Te6edf3release_nameTb4b4b4)Tb4b4b4:
T8b949e"""Find a file in the PlatformIO build output.
The Release/ directory no longer ships pre-built boot component binaries.
All boot components come from PlatformIO build output. Call
ensure_firmware_built() first to trigger a build when needed.
"""
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3build_pathTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3build_path
Tff7b72return Tff7b72None
T8b949e# Forward-compatible aliases (these are now functions, not constants)
Tff7b72def Td2a8ff_bootloader_binTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3BOOTLOADER_BINTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ff_partitions_binTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3PARTITIONS_BINTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ff_firmware_binTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3FIRMWARE_BINTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8fffind_boot_app0Tb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Find boot_app0.bin from PlatformIO framework packages.
Handles versioned package directories (e.g. framework-arduinoespressif32@3.20009.0).
"""
Te6edf3pio_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexpanduserTb4b4b4(Ta5d6ff"Ta5d6ff~/.platformio/packagesTa5d6ff"Tb4b4b4)
T8b949e# Try exact name first
Te6edf3exact Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3pio_dirTb4b4b4, Ta5d6ff"Ta5d6ffframework-arduinoespressif32Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fftoolsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffpartitionsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffboot_app0.binTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3exactTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3exact
T8b949e# Try versioned directories
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisdirTb4b4b4(Te6edf3pio_dirTb4b4b4)Tb4b4b4:
Tff7b72for Te6edf3name Tff7b72in Tffa657sortedTb4b4b4(Te6edf3osTff7b72.Td2a8fflistdirTb4b4b4(Te6edf3pio_dirTb4b4b4)Tb4b4b4, Te6edf3reverseTff7b72=Tff7b72TrueTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3nameTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffframework-arduinoespressif32Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3candidate Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3pio_dirTb4b4b4, Te6edf3nameTb4b4b4, Ta5d6ff"Ta5d6fftoolsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffpartitionsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffboot_app0.binTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3candidateTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3candidate
Tff7b72return Tff7b72None
Tff7b72def Td2a8fffind_bootloaderTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Find bootloader.bin from PlatformIO build output or Release/ bundle."""
Tff7b72return Te6edf3_find_in_platformio_or_releaseTb4b4b4(Te6edf3BOOTLOADER_BINTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffbootloader.binTa5d6ff"Tb4b4b4)
Tff7b72def Td2a8fffind_partitionsTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Find partitions.bin from PlatformIO build output or Release/ bundle."""
Tff7b72return Te6edf3_find_in_platformio_or_releaseTb4b4b4(Te6edf3PARTITIONS_BINTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffpartitions.binTa5d6ff"Tb4b4b4)
Te6edf3BOOT_APP0_BIN Tff7b72= Te6edf3find_boot_app0Tb4b4b4(Tb4b4b4)
T8b949e# ── Board auto-detection ───────────────────────────────────────────────────────
T8b949e# Map chip type to board keys. Chip string comes from esptool "Chip is <type>".
T8b949e# Sorted longest-first in detect_board so "ESP32-S3" wins over "ESP32".
Te6edf3_CHIP_TO_BOARD Tff7b72= Tb4b4b4{
Ta5d6ff"Ta5d6ffESP32-S3Ta5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffv4Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffESP32Ta5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffv3Ta5d6ff"Tb4b4b4,
Tb4b4b4}
Tff7b72def Td2a8ffread_flash_infoTb4b4b4(Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4)Tb4b4b4:
T8b949e"""Read device flash metadata using ``esptool.py flash_id``.
Returns ``(info_dict, None)`` on success, or ``(None, reason)`` on failure.
"""
Te6edf3cmd Tff7b72= Te6edf3esptool_cmd Tff7b72+ Tb4b4b4[Ta5d6ff"Ta5d6ff--portTa5d6ff"Tb4b4b4, Te6edf3portTb4b4b4, Ta5d6ff"Ta5d6ffflash_idTa5d6ff"Tb4b4b4]
Tff7b72tryTb4b4b4:
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(Te6edf3cmdTb4b4b4, Te6edf3capture_outputTff7b72=Tff7b72TrueTb4b4b4, Te6edf3textTff7b72=Tff7b72TrueTb4b4b4, Te6edf3timeoutTff7b72=T79c0ff15Tb4b4b4)
Tff7b72except Te6edf3subprocessTff7b72.Td2a8ffTimeoutExpiredTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6ff"Ta5d6ffesptool timed out (device not responding?)Ta5d6ff"
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tffa657strTb4b4b4(Te6edf3eTb4b4b4)
Te6edf3output Tff7b72= Te6edf3resultTff7b72.Td2a8ffstdout Tff7b72+ Te6edf3resultTff7b72.Td2a8ffstderr
Tff7b72if Te6edf3resultTff7b72.Td2a8ffreturncode Tff7b72!= T79c0ff0Tb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffesptool flash_id failed:Tffea00\nTffd700{Te6edf3outputTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"
T8b949e# Parse key fields
Te6edf3info Tff7b72= Tb4b4b4{Tb4b4b4}
Tff7b72for Te6edf3line Tff7b72in Te6edf3outputTff7b72.Td2a8ffsplitlinesTb4b4b4(Tb4b4b4)Tb4b4b4:
Te6edf3line Tff7b72= Te6edf3lineTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3lineTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffChip is Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3infoTb4b4b4[Ta5d6ff"Ta5d6ffchipTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3lineTb4b4b4[Tffa657lenTb4b4b4(Ta5d6ff"Ta5d6ffChip is Ta5d6ff"Tb4b4b4)Tb4b4b4:Tb4b4b4]
Tff7b72elif Te6edf3lineTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffFeatures:Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3infoTb4b4b4[Ta5d6ff"Ta5d6fffeaturesTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3lineTb4b4b4[Tffa657lenTb4b4b4(Ta5d6ff"Ta5d6ffFeatures:Ta5d6ff"Tb4b4b4)Tb4b4b4:Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72elif Te6edf3lineTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffDetected flash size:Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3infoTb4b4b4[Ta5d6ff"Ta5d6ffflash_sizeTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3lineTff7b72.Td2a8ffsplitTb4b4b4(Ta5d6ff"Ta5d6ff:Ta5d6ff"Tb4b4b4)Tb4b4b4[Tff7b72-T79c0ff1Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72elif Te6edf3lineTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffMAC:Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3infoTb4b4b4[Ta5d6ff"Ta5d6ffmacTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3lineTff7b72.Td2a8ffsplitTb4b4b4(Ta5d6ff"Ta5d6ff:Ta5d6ff"Tb4b4b4)Tb4b4b4[Tff7b72-T79c0ff5Tb4b4b4:Tb4b4b4] T8b949e# last 5 colon-groups
Te6edf3infoTb4b4b4[Ta5d6ff"Ta5d6ffmacTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3lineTb4b4b4[Tffa657lenTb4b4b4(Ta5d6ff"Ta5d6ffMAC:Ta5d6ff"Tb4b4b4)Tb4b4b4:Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72elif Te6edf3lineTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffCrystal isTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3infoTb4b4b4[Ta5d6ff"Ta5d6ffcrystalTa5d6ff"Tb4b4b4] Tff7b72= Te6edf3lineTb4b4b4[Tffa657lenTb4b4b4(Ta5d6ff"Ta5d6ffCrystal isTa5d6ff"Tb4b4b4)Tb4b4b4:Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Te6edf3flash_size Tff7b72= Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffflash_sizeTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3flash_sizeTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffCould not parse flash size from esptool output:Tffea00\nTffd700{Te6edf3outputTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"
Tff7b72return Te6edf3infoTb4b4b4, Tff7b72None
Tff7b72def Td2a8ffdetect_boardTb4b4b4(Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4)Tb4b4b4:
T8b949e"""Auto-detect which Heltec board is connected.
Uses chip type as the primary discriminator (ESP32-S3 → V4, ESP32 → V3),
so a V4 device with 8MB flash is correctly identified as V4, not V3.
Flash size is stored in the returned info dict and used to select the
correct firmware variant.
Returns a tuple (board_key, info_dict) on success, or (None, reason) on
failure. ``board_key`` is "v3" or "v4".
"""
Te6edf3infoTb4b4b4, Te6edf3err Tff7b72= Te6edf3read_flash_infoTb4b4b4(Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4)
Tff7b72if Tff7b72not Te6edf3infoTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Te6edf3err
Te6edf3chip_str Tff7b72= Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffchipTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3features Tff7b72= Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffeaturesTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)
Tff7b72if Ta5d6ff"Ta5d6ffESP32-S3Ta5d6ff" Tff7b72in Te6edf3chip_strTb4b4b4:
T8b949e# Both V3 and V4 are ESP32-S3. Distinguish by PSRAM presence.
T8b949e# V4 (ESP32-S3FH4R2): features includes "Embedded PSRAM"
T8b949e# V3 (ESP32-S3FN8): features has no PSRAM entry
Tff7b72if Ta5d6ff"Ta5d6ffPSRAMTa5d6ff" Tff7b72in Te6edf3featuresTff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4)Tb4b4b4:
Te6edf3board_key Tff7b72= Ta5d6ff"Ta5d6ffv4Ta5d6ff"
Tff7b72elseTb4b4b4:
Te6edf3board_key Tff7b72= Ta5d6ff"Ta5d6ffv3Ta5d6ff"
Tff7b72elif Ta5d6ff"Ta5d6ffESP32Ta5d6ff" Tff7b72in Te6edf3chip_strTb4b4b4:
Te6edf3board_key Tff7b72= Ta5d6ff"Ta5d6ffv3Ta5d6ff"
Tff7b72elseTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffUnknown chip Ta5d6ff'Tffd700{Te6edf3chip_strTffd700}Ta5d6ff'Ta5d6ff — expected ESP32-S3 (V3/V4) or ESP32.Tffea00\nTa5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ffUse --board v3 or --board v4 to specify manually.Ta5d6ff"
Tb4b4b4)
Tff7b72return Te6edf3board_keyTb4b4b4, Te6edf3info
T8b949e# ── Helpers ────────────────────────────────────────────────────────────────────
Tff7b72def Td2a8fffind_esptoolTb4b4b4(Te6edf3prefer_systemTff7b72=Tff7b72FalseTb4b4b4)Tb4b4b4:
T8b949e"""Find esptool, preferring the bundled standalone binary for reproducibility.
Default search order:
1. Release/esptool/esptool (standalone binary — no Python/pyserial needed)
2. PlatformIO packaged esptool.py (dev environments)
3. Host-installed esptool (PATH / ~/.local/bin)
Pass ``prefer_system=True`` (--use-system-esptool) to move the host
installation to the front, useful when you want to use a newer system
esptool for debugging.
The standalone binary MUST remain first in repo_candidates.
"""
T8b949e# Check if pyserial is available — needed for script-based (*.py) fallbacks
Tff7b72tryTb4b4b4:
Tff7b72import T7ee787serial T8b949e# noqa: F401
Te6edf3has_pyserial Tff7b72= Tff7b72True
Tff7b72except Tf85149ImportErrorTb4b4b4:
Te6edf3has_pyserial Tff7b72= Tff7b72False
T8b949e# Platform-aware standalone binary name
Te6edf3exe_name Tff7b72= Ta5d6ff"Ta5d6ffesptool.exeTa5d6ff" Tff7b72if Te6edf3platformTff7b72.Td2a8ffsystemTb4b4b4(Tb4b4b4) Tff7b72== Ta5d6ff"Ta5d6ffWindowsTa5d6ff" Tff7b72else Ta5d6ff"Ta5d6ffesptoolTa5d6ff"
Te6edf3script_dir Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Tff7b72__file__Tb4b4b4)Tb4b4b4)
T8b949e# Two possible locations for the standalone binary:
T8b949e# 1. <script_dir>/esptool/esptool — flat ZIP extraction (user-facing release)
T8b949e# 2. <script_dir>/Release/esptool/esptool — git repo / nested ZIP
Te6edf3bundled_bin Tff7b72= Tb4b4b4(
Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3script_dirTb4b4b4, Ta5d6ff"Ta5d6ffesptoolTa5d6ff"Tb4b4b4, Te6edf3exe_nameTb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3script_dirTb4b4b4, Ta5d6ff"Ta5d6ffesptoolTa5d6ff"Tb4b4b4, Te6edf3exe_nameTb4b4b4)Tb4b4b4)
Tff7b72else Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3script_dirTb4b4b4, Ta5d6ff"Ta5d6ffReleaseTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffesptoolTa5d6ff"Tb4b4b4, Te6edf3exe_nameTb4b4b4)
Tb4b4b4)
Te6edf3pio_esptool Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexpanduserTb4b4b4(
Ta5d6ff"Ta5d6ff~/.platformio/packages/tool-esptoolpy/esptool.pyTa5d6ff"
Tb4b4b4)
Te6edf3repo_candidates Tff7b72= Tb4b4b4[Tb4b4b4]
T8b949e# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
T8b949e# DO NOT CHANGE THIS ORDER.
T8b949e# The bundled Release/esptool binary is INTENTIONALLY first.
T8b949e# It is a platform-native standalone executable — no Python or pyserial
T8b949e# required. Users flashing from the release archive get a consistent,
T8b949e# pinned esptool regardless of what is installed on their machine.
T8b949e# PlatformIO esptool.py is a fallback for dev environments only.
T8b949e# NEVER reorder these lines. NEVER "prefer" PlatformIO over bundled.
T8b949e# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3bundled_binTb4b4b4) Tff7b72and Te6edf3osTff7b72.Td2a8ffaccessTb4b4b4(Te6edf3bundled_binTb4b4b4, Te6edf3osTff7b72.Td2a8ffX_OKTb4b4b4)Tb4b4b4:
T8b949e# Standalone binary — invoke directly, no Python interpreter prefix
Te6edf3repo_candidatesTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Tb4b4b4[Te6edf3bundled_binTb4b4b4]Tb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffbundled esptool binary: Tffd700{Te6edf3bundled_binTffd700}Ta5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3has_pyserial Tff7b72and Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3pio_esptoolTb4b4b4)Tb4b4b4:
Te6edf3repo_candidatesTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Tb4b4b4[Te6edf3sysTff7b72.Td2a8ffexecutableTb4b4b4, Te6edf3pio_esptoolTb4b4b4]Tb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffPlatformIO esptool: Tffd700{Te6edf3pio_esptoolTffd700}Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3system_candidates Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72if Te6edf3shutilTff7b72.Td2a8ffwhichTb4b4b4(Ta5d6ff"Ta5d6ffesptool.pyTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3system_candidatesTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ffesptool.pyTa5d6ff"Tb4b4b4]Tb4b4b4, Ta5d6ff"Ta5d6ffsystem esptool.py from PATHTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3shutilTff7b72.Td2a8ffwhichTb4b4b4(Ta5d6ff"Ta5d6ffesptoolTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3system_candidatesTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Tb4b4b4[Ta5d6ff"Ta5d6ffesptoolTa5d6ff"Tb4b4b4]Tb4b4b4, Ta5d6ff"Ta5d6ffsystem esptool from PATHTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72for Te6edf3candidate Tff7b72in Tb4b4b4[
Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexpanduserTb4b4b4(Ta5d6ff"Ta5d6ff~/.local/bin/esptoolTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffexpanduserTb4b4b4(Ta5d6ff"Ta5d6ff~/.local/bin/esptool.pyTa5d6ff"Tb4b4b4)Tb4b4b4,
Tb4b4b4]Tb4b4b4:
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3candidateTb4b4b4) Tff7b72and Te6edf3osTff7b72.Td2a8ffaccessTb4b4b4(Te6edf3candidateTb4b4b4, Te6edf3osTff7b72.Td2a8ffX_OKTb4b4b4)Tb4b4b4:
Te6edf3system_candidatesTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Tb4b4b4[Te6edf3candidateTb4b4b4]Tb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffuser-local esptool: Tffd700{Te6edf3candidateTffd700}Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3search_order Tff7b72= Te6edf3system_candidates Tff7b72+ Te6edf3repo_candidates Tff7b72if Te6edf3prefer_system Tff7b72else Te6edf3repo_candidates Tff7b72+ Te6edf3system_candidates
Tff7b72for Te6edf3commandTb4b4b4, Te6edf3source Tff7b72in Te6edf3search_orderTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Found Tffd700{Te6edf3sourceTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Te6edf3command
Tff7b72if Tff7b72not Te6edf3has_pyserial Tff7b72and Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3pio_esptoolTb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffFound PlatformIO esptool.py but pyserial is not installed.Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffInstall it with: pip install pyserialTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffOr place the standalone esptool binary at: Release/esptool/esptoolTa5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tff7b72return Tff7b72None
Tff7b72def Td2a8ffesptool_supports_write_verifyTb4b4b4(Te6edf3esptool_cmdTb4b4b4)Tb4b4b4:
T8b949e"""Return True if this esptool build accepts ``write_flash --verify``.
esptool v5 removed ``--verify`` from write-flash, while older releases
still accept it. Probe once and cache the result so flashing can choose
the compatible verification path.
"""
Te6edf3cache_key Tff7b72= Tffa657tupleTb4b4b4(Te6edf3esptool_cmdTb4b4b4)
Tff7b72if Te6edf3cache_key Tff7b72in Te6edf3_esptool_write_verify_supportTb4b4b4:
Tff7b72return Te6edf3_esptool_write_verify_supportTb4b4b4[Te6edf3cache_keyTb4b4b4]
Tff7b72tryTb4b4b4:
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(
Te6edf3esptool_cmd Tff7b72+ Tb4b4b4[Ta5d6ff"Ta5d6ffwrite_flashTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff-hTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3capture_outputTff7b72=Tff7b72TrueTb4b4b4,
Te6edf3textTff7b72=Tff7b72TrueTb4b4b4,
Te6edf3timeoutTff7b72=T79c0ff10Tb4b4b4,
Tb4b4b4)
Te6edf3output Tff7b72= Tb4b4b4(Te6edf3resultTff7b72.Td2a8ffstdout Tff7b72or Ta5d6ff"Ta5d6ff"Tb4b4b4) Tff7b72+ Tb4b4b4(Te6edf3resultTff7b72.Td2a8ffstderr Tff7b72or Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3supported Tff7b72= Ta5d6ff"Ta5d6ff--verifyTa5d6ff" Tff7b72in Te6edf3output
Tff7b72except Tf85149ExceptionTb4b4b4:
Te6edf3supported Tff7b72= Tff7b72False
Te6edf3_esptool_write_verify_supportTb4b4b4[Te6edf3cache_keyTb4b4b4] Tff7b72= Te6edf3supported
Tff7b72return Te6edf3supported
Tff7b72def Td2a8fffind_serial_portTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""List available serial ports and let the user choose."""
Te6edf3system Tff7b72= Te6edf3platformTff7b72.Td2a8ffsystemTb4b4b4(Tb4b4b4)
T8b949e# Gather ports from glob patterns
Tff7b72if Te6edf3system Tff7b72== Ta5d6ff"Ta5d6ffDarwinTa5d6ff"Tb4b4b4:
Te6edf3patterns Tff7b72= Tb4b4b4[Ta5d6ff"Ta5d6ff/dev/cu.usbmodem*Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff/dev/tty.usbmodem*Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff/dev/cu.usbserial*Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff/dev/cu.SLAB*Ta5d6ff"Tb4b4b4]
Tff7b72elif Te6edf3system Tff7b72== Ta5d6ff"Ta5d6ffLinuxTa5d6ff"Tb4b4b4:
Te6edf3patterns Tff7b72= Tb4b4b4[Ta5d6ff"Ta5d6ff/dev/ttyACM*Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff/dev/ttyUSB*Ta5d6ff"Tb4b4b4]
Tff7b72elseTb4b4b4:
Te6edf3patterns Tff7b72= Tb4b4b4[Tb4b4b4]
Te6edf3ports Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3pattern Tff7b72in Te6edf3patternsTb4b4b4:
Te6edf3portsTff7b72.Td2a8ffextendTb4b4b4(Te6edf3globTff7b72.Td2a8ffglobTb4b4b4(Te6edf3patternTb4b4b4)Tb4b4b4)
T8b949e# Also try pyserial's port enumeration (works on all platforms including Windows)
Tff7b72tryTb4b4b4:
Tff7b72import T7ee787serialT7ee787.T7ee787toolsT7ee787.T7ee787list_ports
Tff7b72for Te6edf3port Tff7b72in Te6edf3serialTff7b72.Td2a8fftoolsTff7b72.Td2a8fflist_portsTff7b72.Td2a8ffcomportsTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3portTff7b72.Td2a8ffdevice Tff7b72not Tff7b72in Te6edf3portsTb4b4b4:
Te6edf3portsTff7b72.Td2a8ffappendTb4b4b4(Te6edf3portTff7b72.Td2a8ffdeviceTb4b4b4)
Tff7b72except Tf85149ImportErrorTb4b4b4:
Tff7b72pass
T8b949e# Sort for consistent ordering
Te6edf3portsTff7b72.Td2a8ffsortTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3portsTb4b4b4:
Tff7b72return Tff7b72None
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffAvailable serial ports:Ta5d6ff"Tb4b4b4)
Tff7b72for Te6edf3iTb4b4b4, Te6edf3p Tff7b72in Tffa657enumerateTb4b4b4(Te6edf3portsTb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff [Tffd700{Te6edf3iTff7b72+T79c0ff1Tffd700}Ta5d6ff] Tffd700{Te6edf3pTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Tb4b4b4)
Tff7b72while Tff7b72TrueTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3choice Tff7b72= Tffa657inputTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffSelect port [1-Tffd700{Tffa657lenTb4b4b4(Te6edf3portsTb4b4b4)Tffd700}Ta5d6ff]: Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Te6edf3idx Tff7b72= Tffa657intTb4b4b4(Te6edf3choiceTb4b4b4) Tff7b72- T79c0ff1
Tff7b72if T79c0ff0 Tff7b72<Tff7b72= Te6edf3idx Tff7b72< Tffa657lenTb4b4b4(Te6edf3portsTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3portsTb4b4b4[Te6edf3idxTb4b4b4]
Tff7b72except Tb4b4b4(Tf85149ValueErrorTb4b4b4, Tf85149EOFErrorTb4b4b4)Tb4b4b4:
Tff7b72pass
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffInvalid selection, try again.Ta5d6ff"Tb4b4b4)
Tff7b72def Td2a8ffsha256_fileTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
T8b949e"""Compute SHA-256 hash of a file."""
Te6edf3h Tff7b72= Te6edf3hashlibTff7b72.Td2a8ffsha256Tb4b4b4(Tb4b4b4)
Tff7b72with Tffa657openTb4b4b4(Te6edf3pathTb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Tff7b72for Te6edf3chunk Tff7b72in Tffa657iterTb4b4b4(Tff7b72lambdaTb4b4b4: Te6edf3fTff7b72.Td2a8ffreadTb4b4b4(T79c0ff65536Tb4b4b4)Tb4b4b4, Ta5d6ffbTa5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3hTff7b72.Td2a8ffupdateTb4b4b4(Te6edf3chunkTb4b4b4)
Tff7b72return Te6edf3hTff7b72.Td2a8ffhexdigestTb4b4b4(Tb4b4b4)
Tff7b72def Td2a8ff_cache_dirTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Return the firmware cache directory (next to flash.py)."""
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Tff7b72__file__Tb4b4b4)Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ff.firmware_cacheTa5d6ff"Tb4b4b4)
Tff7b72def Td2a8ff_archive_cache_pathTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Return path to the cached firmware archive zip."""
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3_cache_dirTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3FIRMWARE_ARCHIVETb4b4b4)
Tff7b72def Td2a8ff_extracted_firmware_pathTb4b4b4(Te6edf3firmware_nameTb4b4b4)Tb4b4b4:
T8b949e"""Return path to an extracted firmware binary in the flat cache dir."""
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3_cache_dirTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3firmware_nameTb4b4b4)
Tff7b72def Td2a8ff_cache_meta_pathTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Return path to the archive cache metadata JSON (single file for all variants)."""
Tff7b72return Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3_cache_dirTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffmeta.jsonTa5d6ff"Tb4b4b4)
Tff7b72def Td2a8ff_read_cache_metaTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Read archive cache metadata, returning dict or None if not cached."""
Tff7b72import T7ee787json
Te6edf3path Tff7b72= Te6edf3_cache_meta_pathTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3pathTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Tff7b72with Tffa657openTb4b4b4(Te6edf3pathTb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Tff7b72return Te6edf3jsonTff7b72.Td2a8ffloadTb4b4b4(Te6edf3fTb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72return Tff7b72None
Tff7b72def Td2a8ff_write_cache_metaTb4b4b4(Te6edf3tagTb4b4b4, Te6edf3sha256Tb4b4b4)Tb4b4b4:
T8b949e"""Write archive cache metadata after a successful download."""
Tff7b72import T7ee787json
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3_cache_dirTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Tff7b72with Tffa657openTb4b4b4(Te6edf3_cache_meta_pathTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffwTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3jsonTff7b72.Td2a8ffdumpTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6fftagTa5d6ff"Tb4b4b4: Te6edf3tagTb4b4b4, Ta5d6ff"Ta5d6ffsha256Ta5d6ff"Tb4b4b4: Te6edf3sha256Tb4b4b4}Tb4b4b4, Te6edf3fTb4b4b4, Te6edf3indentTff7b72=T79c0ff2Tb4b4b4)
Tff7b72def Td2a8ff_parse_version_tagTb4b4b4(Te6edf3tagTb4b4b4)Tb4b4b4:
T8b949e"""Parse a version tag like 'v1.0.13' into a tuple (1, 0, 13) for comparison.
Returns None if the tag doesn't match the expected format."""
Tff7b72import T7ee787re
Te6edf3m Tff7b72= Te6edf3reTff7b72.Td2a8ffmatchTb4b4b4(Ta5d6ffrTa5d6ff"Ta5d6ffv?(Ta5d6ff\Ta5d6ffd+)Ta5d6ff\Ta5d6ff.(Ta5d6ff\Ta5d6ffd+)Ta5d6ff\Ta5d6ff.(Ta5d6ff\Ta5d6ffd+)Ta5d6ff"Tb4b4b4, Te6edf3tagTb4b4b4)
Tff7b72if Te6edf3mTb4b4b4:
Tff7b72return Tffa657tupleTb4b4b4(Tffa657intTb4b4b4(Te6edf3xTb4b4b4) Tff7b72for Te6edf3x Tff7b72in Te6edf3mTff7b72.Td2a8ffgroupsTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72return Tff7b72None
Tff7b72def Td2a8ff_fetch_release_infoTb4b4b4(Te6edf3tagTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
T8b949e"""Fetch release info from GitHub. If tag is None, fetches latest."""
Tff7b72tryTb4b4b4:
Tff7b72from T7ee787urllibT7ee787.T7ee787request Tff7b72import Te6edf3urlopenTb4b4b4, Te6edf3Request
Tff7b72import T7ee787json
Tff7b72except Tf85149ImportErrorTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6ff"Ta5d6ffPython urllib not availableTa5d6ff"
Tff7b72if Te6edf3tagTb4b4b4:
T8b949e# Normalize: ensure tag starts with 'v'
Tff7b72if Tff7b72not Te6edf3tagTff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffvTa5d6ff"Tb4b4b4)Tb4b4b4:
Te6edf3tag Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffvTffd700{Te6edf3tagTffd700}Ta5d6ff"
Te6edf3api_url Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffhttps://api.github.com/repos/Tffd700{Te6edf3GITHUB_REPOTffd700}Ta5d6ff/releases/tags/Tffd700{Te6edf3tagTffd700}Ta5d6ff"
Tff7b72elseTb4b4b4:
Te6edf3api_url Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffhttps://api.github.com/repos/Tffd700{Te6edf3GITHUB_REPOTffd700}Ta5d6ff/releases/latestTa5d6ff"
Tff7b72tryTb4b4b4:
Te6edf3req Tff7b72= Te6edf3RequestTb4b4b4(Te6edf3api_urlTb4b4b4, Te6edf3headersTff7b72=Tb4b4b4{Ta5d6ff"Ta5d6ffAcceptTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffapplication/vnd.github+jsonTa5d6ff"Tb4b4b4}Tb4b4b4)
Tff7b72with Te6edf3urlopenTb4b4b4(Te6edf3reqTb4b4b4, Te6edf3timeoutTff7b72=T79c0ff10Tb4b4b4) Tff7b72as Te6edf3respTb4b4b4:
Tff7b72return Te6edf3jsonTff7b72.Td2a8ffloadsTb4b4b4(Te6edf3respTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4, Tff7b72None
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tffa657strTb4b4b4(Te6edf3eTb4b4b4)
Tff7b72def Td2a8fffetch_firmwareTb4b4b4(Te6edf3board_keyTb4b4b4, Te6edf3flash_sizeTb4b4b4, Te6edf3release_tagTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
T8b949e"""Fetch firmware from the GitHub release archive, using cache when possible.
Downloads ``rtnode_firmware.zip`` once and extracts the correct binary for
the given board/flash-size combination. Falls back gracefully to the old
per-board binary if the archive is not present in the release (backward compat).
Returns (firmware_path, release_tag) on success, (None, reason) on failure.
"""
Tff7b72import T7ee787zipfile
Tff7b72from T7ee787urllibT7ee787.T7ee787request Tff7b72import Te6edf3urlretrieve
T8b949e# Resolve variant (fallback to smallest/safest if exact size missing)
Te6edf3variants Tff7b72= Te6edf3BOARD_PROFILESTb4b4b4[Te6edf3board_keyTb4b4b4]Tb4b4b4[Ta5d6ff"Ta5d6ffflash_variantsTa5d6ff"Tb4b4b4]
Tff7b72if Te6edf3flash_size Tff7b72not Tff7b72in Te6edf3variantsTb4b4b4:
Te6edf3flash_size Tff7b72= Tffa657sortedTb4b4b4(Te6edf3variantsTb4b4b4, Te6edf3keyTff7b72=Tff7b72lambda Te6edf3sTb4b4b4: Tffa657intTb4b4b4(Te6edf3sTff7b72.Td2a8ffreplaceTb4b4b4(Ta5d6ff"Ta5d6ffMBTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4[T79c0ff0Tb4b4b4]
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff ⚠ No Tffd700{Te6edf3flash_sizeTffd700}Ta5d6ff variant for Tffd700{Te6edf3board_keyTffd700}Ta5d6ff — using Tffd700{Te6edf3flash_sizeTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3variant Tff7b72= Te6edf3variantsTb4b4b4[Te6edf3flash_sizeTb4b4b4]
Te6edf3firmware_name Tff7b72= Te6edf3variantTb4b4b4[Ta5d6ff"Ta5d6fffirmware_binTa5d6ff"Tb4b4b4]
Te6edf3extracted_path Tff7b72= Te6edf3_extracted_firmware_pathTb4b4b4(Te6edf3firmware_nameTb4b4b4)
Te6edf3archive_path Tff7b72= Te6edf3_archive_cache_pathTb4b4b4(Tb4b4b4)
Te6edf3cache_meta Tff7b72= Te6edf3_read_cache_metaTb4b4b4(Tb4b4b4)
T8b949e# 1. Fetch release info
Te6edf3label Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ffrelease Tffd700{Te6edf3release_tagTffd700}Ta5d6ff" Tff7b72if Te6edf3release_tag Tff7b72else Ta5d6ff"Ta5d6fflatest releaseTa5d6ff"
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffChecking Tffd700{Te6edf3labelTffd700}Ta5d6ff from Tffd700{Te6edf3GITHUB_REPOTffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
Te6edf3releaseTb4b4b4, Te6edf3err Tff7b72= Te6edf3_fetch_release_infoTb4b4b4(Te6edf3release_tagTb4b4b4)
Tff7b72if Tff7b72not Te6edf3releaseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Could not reach GitHub: Tffd700{Te6edf3errTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3cache_meta Tff7b72and Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3extracted_pathTb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Using cached firmware: Tffd700{Te6edf3cache_metaTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fftagTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Te6edf3extracted_pathTb4b4b4, Te6edf3cache_metaTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftagTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffcachedTa5d6ff"Tb4b4b4)
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffNo cached firmware and GitHub unreachable: Tffd700{Te6edf3errTffd700}Ta5d6ff"
Te6edf3remote_tag Tff7b72= Te6edf3releaseTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftag_nameTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffunknownTa5d6ff"Tb4b4b4)
T8b949e# 2. Check whether cached archive is still valid
Te6edf3need_download Tff7b72= Tff7b72True
Tff7b72if Te6edf3cache_meta Tff7b72and Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3archive_pathTb4b4b4)Tb4b4b4:
Te6edf3cached_tag Tff7b72= Te6edf3cache_metaTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fftagTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3cached_tag Tff7b72== Te6edf3remote_tagTb4b4b4:
Tff7b72if Te6edf3sha256_fileTb4b4b4(Te6edf3archive_pathTb4b4b4) Tff7b72== Te6edf3cache_metaTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffsha256Ta5d6ff"Tb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Cached firmware archive is up-to-date: Tffd700{Te6edf3remote_tagTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3need_download Tff7b72= Tff7b72False
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Cache integrity mismatch — re-downloadingTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3cached_ver Tff7b72= Te6edf3_parse_version_tagTb4b4b4(Te6edf3cached_tagTb4b4b4) Tff7b72if Te6edf3cached_tag Tff7b72else Tff7b72None
Te6edf3remote_ver Tff7b72= Te6edf3_parse_version_tagTb4b4b4(Te6edf3remote_tagTb4b4b4)
Tff7b72if Te6edf3cached_ver Tff7b72and Te6edf3remote_ver Tff7b72and Te6edf3remote_ver Tff7b72> Te6edf3cached_verTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Newer version available: Tffd700{Te6edf3cached_tagTffd700}Ta5d6ff → Tffd700{Te6edf3remote_tagTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3cached_ver Tff7b72and Te6edf3remote_ver Tff7b72and Te6edf3remote_ver Tff7b72< Te6edf3cached_verTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Requested version Tffd700{Te6edf3remote_tagTffd700}Ta5d6ff is older than cached Tffd700{Te6edf3cached_tagTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Version changed: Tffd700{Te6edf3cached_tagTffd700}Ta5d6ff → Tffd700{Te6edf3remote_tagTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3need_downloadTb4b4b4:
T8b949e# 3. Locate the archive asset (with per-board fallback for old releases)
Te6edf3asset_url Tff7b72= Tff7b72None
Te6edf3fallback_url Tff7b72= Tff7b72None
Te6edf3fallback_name Tff7b72= Tff7b72None
Tff7b72for Te6edf3asset Tff7b72in Te6edf3releaseTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffassetsTa5d6ff"Tb4b4b4, Tb4b4b4[Tb4b4b4]Tb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3assetTb4b4b4[Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4] Tff7b72== Te6edf3FIRMWARE_ARCHIVETb4b4b4:
Te6edf3asset_url Tff7b72= Te6edf3assetTb4b4b4[Ta5d6ff"Ta5d6ffbrowser_download_urlTa5d6ff"Tb4b4b4]
Tff7b72if Te6edf3assetTb4b4b4[Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4] Tff7b72== Te6edf3firmware_nameTb4b4b4:
Te6edf3fallback_url Tff7b72= Te6edf3assetTb4b4b4[Ta5d6ff"Ta5d6ffbrowser_download_urlTa5d6ff"Tb4b4b4]
Te6edf3fallback_name Tff7b72= Te6edf3assetTb4b4b4[Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4]
Te6edf3osTff7b72.Td2a8ffmakedirsTb4b4b4(Te6edf3_cache_dirTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3exist_okTff7b72=Tff7b72TrueTb4b4b4)
Tff7b72if Te6edf3asset_urlTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Downloading Tffd700{Te6edf3remote_tagTffd700}Ta5d6ff / Tffd700{Te6edf3FIRMWARE_ARCHIVETffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3urlretrieveTb4b4b4(Te6edf3asset_urlTb4b4b4, Te6edf3archive_pathTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffDownload failed: Tffd700{Te6edf3eTffd700}Ta5d6ff"
Te6edf3file_sha Tff7b72= Te6edf3sha256_fileTb4b4b4(Te6edf3archive_pathTb4b4b4)
Te6edf3_write_cache_metaTb4b4b4(Te6edf3remote_tagTb4b4b4, Te6edf3file_shaTb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Downloaded Tffd700{Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3archive_pathTb4b4b4)Tffd700:Ta5d6ff,Tffd700}Ta5d6ff bytes SHA-256: Tffd700{Te6edf3file_shaTb4b4b4[Tb4b4b4:T79c0ff16Tb4b4b4]Tffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3fallback_urlTb4b4b4:
T8b949e# Old-style release — download the individual binary directly
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Archive Ta5d6ff'Tffd700{Te6edf3FIRMWARE_ARCHIVETffd700}Ta5d6ff'Ta5d6ff not in release — downloading Tffd700{Te6edf3fallback_nameTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3urlretrieveTb4b4b4(Te6edf3fallback_urlTb4b4b4, Te6edf3extracted_pathTb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffDownload failed: Tffd700{Te6edf3eTffd700}Ta5d6ff"
Te6edf3file_sha Tff7b72= Te6edf3sha256_fileTb4b4b4(Te6edf3extracted_pathTb4b4b4)
Te6edf3_write_cache_metaTb4b4b4(Te6edf3remote_tagTb4b4b4, Te6edf3file_shaTb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Downloaded Tffd700{Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3extracted_pathTb4b4b4)Tffd700:Ta5d6ff,Tffd700}Ta5d6ff bytes SHA-256: Tffd700{Te6edf3file_shaTb4b4b4[Tb4b4b4:T79c0ff16Tb4b4b4]Tffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
Tff7b72return Te6edf3extracted_pathTb4b4b4, Te6edf3remote_tag
Tff7b72elseTb4b4b4:
Te6edf3available Tff7b72= Tb4b4b4[Te6edf3aTb4b4b4[Ta5d6ff"Ta5d6ffnameTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3a Tff7b72in Te6edf3releaseTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffassetsTa5d6ff"Tb4b4b4, Tb4b4b4[Tb4b4b4]Tb4b4b4)Tb4b4b4]
Tff7b72return Tff7b72NoneTb4b4b4, Tb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffNeither Ta5d6ff'Tffd700{Te6edf3FIRMWARE_ARCHIVETffd700}Ta5d6ff'Ta5d6ff nor Ta5d6ff'Tffd700{Te6edf3firmware_nameTffd700}Ta5d6ff'Ta5d6ff found in release Tffd700{Te6edf3remote_tagTffd700}Ta5d6ff.Tffea00\nTa5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ff Available assets: Tffd700{Te6edf3availableTffd700}Ta5d6ff"
Tb4b4b4)
T8b949e# 4. Extract the correct variant from the archive.
T8b949e# Prefer the pre-merged binary — it is self-contained (bootloader + partitions
T8b949e# + app in one file) so flash.py never needs PlatformIO or boot components.
T8b949e# Fall back to the app-only binary for backward compat with old archives.
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3archive_pathTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffArchive not found: Tffd700{Te6edf3archive_pathTffd700}Ta5d6ff"
Tff7b72tryTb4b4b4:
Tff7b72with Te6edf3zipfileTff7b72.Td2a8ffZipFileTb4b4b4(Te6edf3archive_pathTb4b4b4) Tff7b72as Te6edf3zfTb4b4b4:
Te6edf3names Tff7b72= Te6edf3zfTff7b72.Td2a8ffnamelistTb4b4b4(Tb4b4b4)
T8b949e# Try merged binary first
Te6edf3merged_name Tff7b72= Te6edf3variantTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmerged_binTa5d6ff"Tb4b4b4)
Te6edf3merged_path Tff7b72= Te6edf3_extracted_firmware_pathTb4b4b4(Te6edf3merged_nameTb4b4b4) Tff7b72if Te6edf3merged_name Tff7b72else Tff7b72None
Tff7b72if Te6edf3merged_name Tff7b72and Te6edf3merged_name Tff7b72in Te6edf3namesTb4b4b4:
Tff7b72with Te6edf3zfTff7b72.Td2a8ffopenTb4b4b4(Te6edf3merged_nameTb4b4b4) Tff7b72as Te6edf3srcTb4b4b4, Tffa657openTb4b4b4(Te6edf3merged_pathTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3dstTb4b4b4:
Te6edf3dstTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3srcTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Extracted Tffd700{Te6edf3merged_nameTffd700}Ta5d6ff (pre-merged, full-flash-ready)Ta5d6ff"Tb4b4b4)
Tff7b72return Te6edf3merged_pathTb4b4b4, Te6edf3remote_tag
T8b949e# Fall back to app-only binary
Tff7b72if Te6edf3firmware_name Tff7b72not Tff7b72in Te6edf3namesTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Tb4b4b4(
Ta5d6fffTa5d6ff"Ta5d6ffNeither Ta5d6ff'Tffd700{Te6edf3merged_name Tff7b72or Ta5d6ff'Ta5d6ff?Ta5d6ff'Tffd700}Ta5d6ff'Ta5d6ff nor Ta5d6ff'Tffd700{Te6edf3firmware_nameTffd700}Ta5d6ff'Ta5d6ff found in archive.Tffea00\nTa5d6ff"
Ta5d6fffTa5d6ff"Ta5d6ff Archive contains: Tffd700{Te6edf3namesTffd700}Ta5d6ff"
Tb4b4b4)
Tff7b72with Te6edf3zfTff7b72.Td2a8ffopenTb4b4b4(Te6edf3firmware_nameTb4b4b4) Tff7b72as Te6edf3srcTb4b4b4, Tffa657openTb4b4b4(Te6edf3extracted_pathTb4b4b4, Ta5d6ff"Ta5d6ffwbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3dstTb4b4b4:
Te6edf3dstTff7b72.Td2a8ffwriteTb4b4b4(Te6edf3srcTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Extracted Tffd700{Te6edf3firmware_nameTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffFailed to extract firmware from archive: Tffd700{Te6edf3eTffd700}Ta5d6ff"
Tff7b72return Te6edf3extracted_pathTb4b4b4, Te6edf3remote_tag
Tff7b72def Td2a8ff_do_mergeTb4b4b4(Te6edf3output_pathTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3bootloaderTb4b4b4, Te6edf3partitionsTb4b4b4, Te6edf3boot_app0Tb4b4b4, Te6edf3firmwareTb4b4b4)Tb4b4b4:
T8b949e"""Low-level merge: combine the four components into a single binary."""
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffMerging firmware components...Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Bootloader: Tffd700{Te6edf3bootloaderTffd700}Ta5d6ff @ 0xTffd700{Te6edf3BOOTLOADER_ADDRTffd700:Ta5d6ff04xTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Partitions: Tffd700{Te6edf3partitionsTffd700}Ta5d6ff @ 0xTffd700{Te6edf3PARTITIONS_ADDRTffd700:Ta5d6ff04xTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff boot_app0: Tffd700{Te6edf3boot_app0Tffd700}Ta5d6ff @ 0xTffd700{Te6edf3BOOT_APP0_ADDRTffd700:Ta5d6ff04xTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Firmware: Tffd700{Te6edf3firmwareTffd700}Ta5d6ff @ 0xTffd700{Te6edf3APP_ADDRTffd700:Ta5d6ff05xTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3flash_mode Tff7b72= Te6edf3BOARD_FLASH_MODETb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Flash mode: Tffd700{Te6edf3flash_modeTff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Te6edf3cmd Tff7b72= Te6edf3esptool_cmd Tff7b72+ Tb4b4b4[
Ta5d6ff"Ta5d6ff--chipTa5d6ff"Tb4b4b4, Te6edf3CHIPTb4b4b4,
Ta5d6ff"Ta5d6ffmerge_binTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff--flash_modeTa5d6ff"Tb4b4b4, Te6edf3flash_modeTb4b4b4,
Ta5d6ff"Ta5d6ff--flash_freqTa5d6ff"Tb4b4b4, Te6edf3FLASH_FREQTb4b4b4,
Ta5d6ff"Ta5d6ff--flash_sizeTa5d6ff"Tb4b4b4, Te6edf3FLASH_SIZETb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ff-oTa5d6ff"Tb4b4b4, Te6edf3output_pathTb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3BOOTLOADER_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff"Tb4b4b4, Te6edf3bootloaderTb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3PARTITIONS_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff"Tb4b4b4, Te6edf3partitionsTb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3BOOT_APP0_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff"Tb4b4b4, Te6edf3boot_app0Tb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3APP_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff"Tb4b4b4, Te6edf3firmwareTb4b4b4,
Tb4b4b4]
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(Te6edf3cmdTb4b4b4, Te6edf3capture_outputTff7b72=Tff7b72TrueTb4b4b4, Te6edf3textTff7b72=Tff7b72TrueTb4b4b4)
Tff7b72if Te6edf3resultTff7b72.Td2a8ffreturncode Tff7b72!= T79c0ff0Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffError merging: Tffd700{Te6edf3resultTff7b72.Td2a8ffstderrTffd700}Tffd700{Te6edf3resultTff7b72.Td2a8ffstdoutTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Te6edf3size Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3output_pathTb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ffMerged binary: Tffd700{Te6edf3output_pathTffd700}Ta5d6ff (Tffd700{Te6edf3sizeTffd700:Ta5d6ff,Tffd700}Ta5d6ff bytes)Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffSHA-256: Tffd700{Te6edf3sha256_fileTb4b4b4(Te6edf3output_pathTb4b4b4)Tb4b4b4[Tb4b4b4:T79c0ff16Tb4b4b4]Tffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72True
Tff7b72def Td2a8ffensure_firmware_builtTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Ensure PlatformIO firmware and boot components exist, building if needed.
Returns True if all build artifacts are available (existing or freshly
built), False if PlatformIO is not installed or the build fails.
"""
Te6edf3firmware Tff7b72= Te6edf3FIRMWARE_BINTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3firmwareTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72True
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ff Firmware not found: Tffd700{Te6edf3firmwareTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Building with PlatformIO (env: Tffd700{Te6edf3PIO_ENVTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff)...Ta5d6ff"Tb4b4b4)
Te6edf3pio Tff7b72= Te6edf3shutilTff7b72.Td2a8ffwhichTb4b4b4(Ta5d6ff"Ta5d6ffpioTa5d6ff"Tb4b4b4) Tff7b72or Te6edf3shutilTff7b72.Td2a8ffwhichTb4b4b4(Ta5d6ff"Ta5d6ffplatformioTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3pioTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Error: PlatformIO not found. Install from https://platformio.orgTa5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(Tb4b4b4[Te6edf3pioTb4b4b4, Ta5d6ff"Ta5d6ffrunTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff-eTa5d6ff"Tb4b4b4, Te6edf3PIO_ENVTb4b4b4(Tb4b4b4)Tb4b4b4]Tb4b4b4,
Te6edf3cwdTff7b72=Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffdirnameTb4b4b4(Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffabspathTb4b4b4(Tff7b72__file__Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3resultTff7b72.Td2a8ffreturncode Tff7b72!= T79c0ff0Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff PlatformIO build failed.Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3firmwareTb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Error: Build succeeded but firmware not found: Tffd700{Te6edf3firmwareTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Build complete.Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72True
Tff7b72def Td2a8ffmerge_firmwareTb4b4b4(Te6edf3output_pathTb4b4b4, Te6edf3esptool_cmdTb4b4b4)Tb4b4b4:
T8b949e"""Merge bootloader + partitions + boot_app0 + app into a single binary.
Uses PlatformIO build output. Triggers a build automatically if the
firmware binary is not present.
"""
Te6edf3ensure_firmware_builtTb4b4b4(Tb4b4b4)
Te6edf3bootloader Tff7b72= Te6edf3find_bootloaderTb4b4b4(Tb4b4b4)
Te6edf3partitions Tff7b72= Te6edf3find_partitionsTb4b4b4(Tb4b4b4)
Te6edf3boot_app0 Tff7b72= Te6edf3BOOT_APP0_BIN
Te6edf3firmware Tff7b72= Te6edf3FIRMWARE_BINTb4b4b4(Tb4b4b4)
Te6edf3missing Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72if Tff7b72not Te6edf3bootloaderTb4b4b4: Te6edf3missingTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Ta5d6ff"Ta5d6ffbootloaderTa5d6ff"Tb4b4b4, Te6edf3BOOTLOADER_BINTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3partitionsTb4b4b4: Te6edf3missingTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Ta5d6ff"Ta5d6ffpartitionsTa5d6ff"Tb4b4b4, Te6edf3PARTITIONS_BINTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3boot_app0Tb4b4b4: Te6edf3missingTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Ta5d6ff"Ta5d6ffboot_app0Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff(not found)Ta5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3firmwareTb4b4b4)Tb4b4b4:
Te6edf3missingTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Ta5d6ff"Ta5d6fffirmwareTa5d6ff"Tb4b4b4, Te6edf3firmwareTb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3missingTb4b4b4:
Tff7b72for Te6edf3nameTb4b4b4, Te6edf3path Tff7b72in Te6edf3missingTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffError: Tffd700{Te6edf3nameTffd700}Ta5d6ff not found: Tffd700{Te6edf3pathTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffRun Ta5d6ff'Ta5d6ffpio run -e Tffd700{Te6edf3PIO_ENVTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff'Ta5d6ff to build first.Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Tff7b72return Te6edf3_do_mergeTb4b4b4(Te6edf3output_pathTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3bootloaderTb4b4b4, Te6edf3partitionsTb4b4b4, Te6edf3boot_app0Tb4b4b4, Te6edf3firmwareTb4b4b4)
Tff7b72def Td2a8ffauto_merge_app_binaryTb4b4b4(Te6edf3app_binary_pathTb4b4b4, Te6edf3esptool_cmdTb4b4b4)Tb4b4b4:
T8b949e"""Auto-merge an app-only binary with boot components for a full flash.
Finds bootloader, partitions, and boot_app0 from PlatformIO build output.
Triggers a PlatformIO build automatically if firmware is not yet built,
since bootloader.bin and partitions.bin come from the same build.
Returns the path to the merged binary on success, or None on failure.
"""
Te6edf3ensure_firmware_builtTb4b4b4(Tb4b4b4)
Te6edf3bootloader Tff7b72= Te6edf3find_bootloaderTb4b4b4(Tb4b4b4)
Te6edf3partitions Tff7b72= Te6edf3find_partitionsTb4b4b4(Tb4b4b4)
Te6edf3boot_app0 Tff7b72= Te6edf3BOOT_APP0_BIN
Te6edf3missing Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72if Tff7b72not Te6edf3bootloaderTb4b4b4: Te6edf3missingTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffbootloader.binTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3partitionsTb4b4b4: Te6edf3missingTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffpartitions.binTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3boot_app0Tb4b4b4: Te6edf3missingTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ffboot_app0.binTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3missingTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffCannot auto-merge: missing Tffd700{Ta5d6ff'Ta5d6ff, Ta5d6ff'Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3missingTb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffBuild with PlatformIO first: pio run -e Tffd700{Te6edf3PIO_ENVTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72None
T8b949e# Create merged binary next to the app binary
Te6edf3baseTb4b4b4, Te6edf3ext Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffsplitextTb4b4b4(Te6edf3app_binary_pathTb4b4b4)
Te6edf3merged_path Tff7b72= Ta5d6fffTa5d6ff"Tffd700{Te6edf3baseTffd700}Ta5d6ff_mergedTffd700{Te6edf3extTffd700}Ta5d6ff"
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffAuto-merging app-only binary with boot components...Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3_do_mergeTb4b4b4(Te6edf3merged_pathTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3bootloaderTb4b4b4, Te6edf3partitionsTb4b4b4, Te6edf3boot_app0Tb4b4b4, Te6edf3app_binary_pathTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3merged_path
Tff7b72return Tff7b72None
Tff7b72def Td2a8ffread_device_partitionsTb4b4b4(Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
T8b949e"""Read the partition table from the connected device.
Uses esptool read_flash to read PARTITION_TABLE_SIZE bytes from
PARTITIONS_ADDR (0x8000).
Returns the raw bytes on success, or None on failure.
"""
Tff7b72import T7ee787tempfile
Tff7b72if Te6edf3baud Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3baud Tff7b72= Te6edf3BAUD_RATETb4b4b4(Tb4b4b4)
Te6edf3tmp Tff7b72= Te6edf3tempfileTff7b72.Td2a8ffNamedTemporaryFileTb4b4b4(Te6edf3suffixTff7b72=Ta5d6ff"Ta5d6ff.binTa5d6ff"Tb4b4b4, Te6edf3deleteTff7b72=Tff7b72FalseTb4b4b4)
Te6edf3tmpTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3cmd Tff7b72= Te6edf3esptool_cmd Tff7b72+ Tb4b4b4[
Ta5d6ff"Ta5d6ff--chipTa5d6ff"Tb4b4b4, Te6edf3CHIPTb4b4b4,
Ta5d6ff"Ta5d6ff--portTa5d6ff"Tb4b4b4, Te6edf3portTb4b4b4,
Ta5d6ff"Ta5d6ff--baudTa5d6ff"Tb4b4b4, Te6edf3baudTb4b4b4,
Ta5d6ff"Ta5d6ffread_flashTa5d6ff"Tb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3PARTITIONS_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff"Tb4b4b4,
Tffa657strTb4b4b4(Te6edf3PARTITION_TABLE_SIZETb4b4b4)Tb4b4b4,
Te6edf3tmpTff7b72.Td2a8ffnameTb4b4b4,
Tb4b4b4]
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(Te6edf3cmdTb4b4b4, Te6edf3capture_outputTff7b72=Tff7b72TrueTb4b4b4, Te6edf3textTff7b72=Tff7b72TrueTb4b4b4, Te6edf3timeoutTff7b72=T79c0ff30Tb4b4b4)
Tff7b72if Te6edf3resultTff7b72.Td2a8ffreturncode Tff7b72!= T79c0ff0Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72with Tffa657openTb4b4b4(Te6edf3tmpTff7b72.Td2a8ffnameTb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Tff7b72return Te6edf3fTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72finallyTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3osTff7b72.Td2a8ffunlinkTb4b4b4(Te6edf3tmpTff7b72.Td2a8ffnameTb4b4b4)
Tff7b72except Tf85149OSErrorTb4b4b4:
Tff7b72pass
Tff7b72def Td2a8ffcheck_partition_tableTb4b4b4(Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
T8b949e"""Compare the device's partition table against the expected one.
Returns:
True — partition table matches (or no expected table to compare against)
False — partition table mismatch or unreadable state (device needs full flash)
"""
Te6edf3expected_path Tff7b72= Te6edf3find_partitionsTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3expected_pathTb4b4b4:
T8b949e# Can't check — no reference partition table available
Tff7b72return Tff7b72True
Tff7b72with Tffa657openTb4b4b4(Te6edf3expected_pathTb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3expected Tff7b72= Te6edf3fTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffChecking device partition table...Ta5d6ff"Tb4b4b4)
Te6edf3device_data Tff7b72= Te6edf3read_device_partitionsTb4b4b4(Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTb4b4b4)
Tff7b72if Te6edf3device_data Tff7b72is Tff7b72NoneTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Could not read partition table from deviceTa5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
T8b949e# Compare only the meaningful portion (both should be PARTITION_TABLE_SIZE)
Tff7b72if Te6edf3device_dataTb4b4b4[Tb4b4b4:Tffa657lenTb4b4b4(Te6edf3expectedTb4b4b4)Tb4b4b4] Tff7b72== Te6edf3expectedTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Partition table OK ✓Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72True
T8b949e# Check if device has any valid partition table at all
Tff7b72if Te6edf3device_dataTb4b4b4[Tb4b4b4:T79c0ff2Tb4b4b4] Tff7b72!= Te6edf3PARTITION_TABLE_MAGICTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff No valid partition table found on device (blank or corrupted)Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Partition table MISMATCH — device has a different layoutTa5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Tff7b72def Td2a8ffcheck_app_on_deviceTb4b4b4(Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
T8b949e"""Check whether app firmware is present on the device.
Reads a small chunk from APP_ADDR (0x10000). If the region is all 0xFF
(erased flash), no app is present and the device needs a full flash.
Returns True if app firmware is detected, False if blank/absent or unreadable.
"""
Tff7b72import T7ee787tempfile
Tff7b72if Te6edf3baud Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3baud Tff7b72= Te6edf3BAUD_RATETb4b4b4(Tb4b4b4)
Te6edf3read_size Tff7b72= T79c0ff256 T8b949e# enough to distinguish blank from real firmware
Te6edf3tmp Tff7b72= Te6edf3tempfileTff7b72.Td2a8ffNamedTemporaryFileTb4b4b4(Te6edf3suffixTff7b72=Ta5d6ff"Ta5d6ff.binTa5d6ff"Tb4b4b4, Te6edf3deleteTff7b72=Tff7b72FalseTb4b4b4)
Te6edf3tmpTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3cmd Tff7b72= Te6edf3esptool_cmd Tff7b72+ Tb4b4b4[
Ta5d6ff"Ta5d6ff--chipTa5d6ff"Tb4b4b4, Te6edf3CHIPTb4b4b4,
Ta5d6ff"Ta5d6ff--portTa5d6ff"Tb4b4b4, Te6edf3portTb4b4b4,
Ta5d6ff"Ta5d6ff--baudTa5d6ff"Tb4b4b4, Te6edf3baudTb4b4b4,
Ta5d6ff"Ta5d6ffread_flashTa5d6ff"Tb4b4b4,
Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3APP_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff"Tb4b4b4,
Tffa657strTb4b4b4(Te6edf3read_sizeTb4b4b4)Tb4b4b4,
Te6edf3tmpTff7b72.Td2a8ffnameTb4b4b4,
Tb4b4b4]
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(Te6edf3cmdTb4b4b4, Te6edf3capture_outputTff7b72=Tff7b72TrueTb4b4b4, Te6edf3textTff7b72=Tff7b72TrueTb4b4b4, Te6edf3timeoutTff7b72=T79c0ff30Tb4b4b4)
Tff7b72if Te6edf3resultTff7b72.Td2a8ffreturncode Tff7b72!= T79c0ff0Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Warning: Could not read app region from deviceTa5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Tff7b72with Tffa657openTb4b4b4(Te6edf3tmpTff7b72.Td2a8ffnameTb4b4b4, Ta5d6ff"Ta5d6ffrbTa5d6ff"Tb4b4b4) Tff7b72as Te6edf3fTb4b4b4:
Te6edf3data Tff7b72= Te6edf3fTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)
T8b949e# All 0xFF means flash is blank — no app present
Tff7b72if Te6edf3data Tff7b72== Ta5d6ffbTa5d6ff'Tffea00\xffTa5d6ff' Tff7b72* Tffa657lenTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72False
Tff7b72return Tff7b72True
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Warning: App check failed: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Tff7b72finallyTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3osTff7b72.Td2a8ffunlinkTb4b4b4(Te6edf3tmpTff7b72.Td2a8ffnameTb4b4b4)
Tff7b72except Tf85149OSErrorTb4b4b4:
Tff7b72pass
Tff7b72def Td2a8ffreset_to_bootloaderTb4b4b4(Te6edf3portTb4b4b4)Tb4b4b4:
T8b949e"""Open serial port at 1200 baud to trigger ESP32-S3 USB bootloader reset.
Many ESP32-S3 boards with native USB will enter download mode when
the port is opened at 1200 baud with DTR toggled. This is useful
when the device is stuck or unresponsive to normal esptool connection.
"""
Tff7b72tryTb4b4b4:
Tff7b72import T7ee787serial
Tff7b72except Tf85149ImportErrorTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffError: pyserial is required for 1200 baud reset.Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffInstall it with: pip install pyserialTa5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffOpening Tffd700{Te6edf3portTffd700}Ta5d6ff at 1200 baud to trigger bootloader...Ta5d6ff"Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3ser Tff7b72= Te6edf3serialTff7b72.Td2a8ffSerialTb4b4b4(Te6edf3portTb4b4b4, T79c0ff1200Tb4b4b4)
Te6edf3serTff7b72.Td2a8ffdtr Tff7b72= Tff7b72False
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.1Tb4b4b4)
Te6edf3serTff7b72.Td2a8ffdtr Tff7b72= Tff7b72True
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff0.1Tb4b4b4)
Te6edf3serTff7b72.Td2a8ffdtr Tff7b72= Tff7b72False
Te6edf3serTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffError: Tffd700{Te6edf3eTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72False
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffWaiting for device to re-enumerate in download mode...Ta5d6ff"Tb4b4b4)
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff3Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffDone. The device should now be in download mode.Ta5d6ff"Tb4b4b4)
Tff7b72return Tff7b72True
Tff7b72def Td2a8ffverify_firmwareTb4b4b4(Te6edf3firmware_pathTb4b4b4, Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3flash_modeTff7b72=Tff7b72NoneTb4b4b4, Te6edf3no_hard_resetTff7b72=Tff7b72FalseTb4b4b4)Tb4b4b4:
T8b949e"""Verify flashed firmware using esptool's dedicated verify command."""
Tff7b72if Te6edf3baud Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3baud Tff7b72= Te6edf3BAUD_RATETb4b4b4(Tb4b4b4)
Te6edf3flash_size Tff7b72= Te6edf3FLASH_SIZETb4b4b4(Tb4b4b4)
Te6edf3mode Tff7b72= Te6edf3flash_mode Tff7b72or Te6edf3BOARD_FLASH_MODETb4b4b4(Tb4b4b4)
Te6edf3is_merged Tff7b72= Te6edf3is_merged_binaryTb4b4b4(Te6edf3firmware_pathTb4b4b4)
Te6edf3flash_addr Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3BOOTLOADER_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff" Tff7b72if Te6edf3is_merged Tff7b72else Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3APP_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff"
Te6edf3after_arg Tff7b72= Ta5d6ff"Ta5d6ffno_resetTa5d6ff" Tff7b72if Te6edf3no_hard_reset Tff7b72else Ta5d6ff"Ta5d6ffhard_resetTa5d6ff"
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffVerifying flashed firmware...Ta5d6ff"Tb4b4b4)
Te6edf3cmd Tff7b72= Te6edf3esptool_cmd Tff7b72+ Tb4b4b4[
Ta5d6ff"Ta5d6ff--chipTa5d6ff"Tb4b4b4, Te6edf3CHIPTb4b4b4,
Ta5d6ff"Ta5d6ff--portTa5d6ff"Tb4b4b4, Te6edf3portTb4b4b4,
Ta5d6ff"Ta5d6ff--baudTa5d6ff"Tb4b4b4, Te6edf3baudTb4b4b4,
Ta5d6ff"Ta5d6ff--beforeTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffno_resetTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff--afterTa5d6ff"Tb4b4b4, Te6edf3after_argTb4b4b4,
Ta5d6ff"Ta5d6ffverify_flashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff--flash_modeTa5d6ff"Tb4b4b4, Te6edf3modeTb4b4b4,
Ta5d6ff"Ta5d6ff--flash_freqTa5d6ff"Tb4b4b4, Te6edf3FLASH_FREQTb4b4b4,
Ta5d6ff"Ta5d6ff--flash_sizeTa5d6ff"Tb4b4b4, Te6edf3flash_sizeTb4b4b4,
Te6edf3flash_addrTb4b4b4, Te6edf3firmware_pathTb4b4b4,
Tb4b4b4]
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffRunning: Ta5d6ff" Tff7b72+ Ta5d6ff"Ta5d6ff Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3cmdTb4b4b4[Tff7b72-T79c0ff8Tb4b4b4:Tb4b4b4]Tb4b4b4)Tb4b4b4)
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(Te6edf3cmdTb4b4b4)
Tff7b72return Te6edf3resultTff7b72.Td2a8ffreturncode Tff7b72== T79c0ff0
Tff7b72def Td2a8ffflash_firmwareTb4b4b4(Te6edf3firmware_pathTb4b4b4, Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3no_reset_beforeTff7b72=Tff7b72FalseTb4b4b4, Te6edf3verifyTff7b72=Tff7b72FalseTb4b4b4,
Te6edf3flash_modeTff7b72=Tff7b72NoneTb4b4b4, Te6edf3no_hard_resetTff7b72=Tff7b72FalseTb4b4b4)Tb4b4b4:
T8b949e"""Flash firmware to the device.
Args:
no_reset_before: If True, use ``--before no_reset`` so we don't try to
re-enter download mode (device is already in stub after erase).
verify: If True, add ``--verify`` for read-back verification.
flash_mode: Override flash mode (default: board profile).
no_hard_reset: If True, use ``--after no_reset`` to keep device in stub.
"""
Tff7b72if Te6edf3baud Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3baud Tff7b72= Te6edf3BAUD_RATETb4b4b4(Tb4b4b4)
Te6edf3flash_size Tff7b72= Te6edf3FLASH_SIZETb4b4b4(Tb4b4b4)
Te6edf3mode Tff7b72= Te6edf3flash_mode Tff7b72or Te6edf3BOARD_FLASH_MODETb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ffFlashing Tffd700{Te6edf3firmware_pathTffd700}Ta5d6ff to Tffd700{Te6edf3portTffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Chip: Tffd700{Te6edf3CHIPTffd700}Ta5d6ff Baud: Tffd700{Te6edf3baudTffd700}Ta5d6ff Flash: Tffd700{Te6edf3flash_sizeTffd700}Ta5d6ff Mode: Tffd700{Te6edf3modeTff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4)Tffd700}Tffea00\nTa5d6ff"Tb4b4b4)
T8b949e# Determine if this is a merged binary (flash at 0x0) or app-only (flash at 0x10000)
Te6edf3is_merged Tff7b72= Te6edf3is_merged_binaryTb4b4b4(Te6edf3firmware_pathTb4b4b4)
Tff7b72if Te6edf3is_mergedTb4b4b4:
Te6edf3flash_addr Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3BOOTLOADER_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff"
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Detected: merged binary (partition table at 0x8000) -> flash at Tffd700{Te6edf3flash_addrTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3flash_addr Tff7b72= Ta5d6fffTa5d6ff"Ta5d6ff0xTffd700{Te6edf3APP_ADDRTffd700:Ta5d6ffxTffd700}Ta5d6ff"
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Detected: app-only binary -> flash at Tffd700{Te6edf3flash_addrTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3inline_verify Tff7b72= Te6edf3verify Tff7b72and Te6edf3esptool_supports_write_verifyTb4b4b4(Te6edf3esptool_cmdTb4b4b4)
Te6edf3post_write_verify Tff7b72= Te6edf3verify Tff7b72and Tff7b72not Te6edf3inline_verify
Te6edf3before_arg Tff7b72= Ta5d6ff"Ta5d6ffno_resetTa5d6ff" Tff7b72if Te6edf3no_reset_before Tff7b72else Ta5d6ff"Ta5d6ffdefault_resetTa5d6ff"
Te6edf3after_arg Tff7b72= Ta5d6ff"Ta5d6ffno_resetTa5d6ff" Tff7b72if Tb4b4b4(Te6edf3no_hard_reset Tff7b72or Te6edf3post_write_verifyTb4b4b4) Tff7b72else Ta5d6ff"Ta5d6ffhard_resetTa5d6ff"
Te6edf3cmd Tff7b72= Te6edf3esptool_cmd Tff7b72+ Tb4b4b4[
Ta5d6ff"Ta5d6ff--chipTa5d6ff"Tb4b4b4, Te6edf3CHIPTb4b4b4,
Ta5d6ff"Ta5d6ff--portTa5d6ff"Tb4b4b4, Te6edf3portTb4b4b4,
Ta5d6ff"Ta5d6ff--baudTa5d6ff"Tb4b4b4, Te6edf3baudTb4b4b4,
Ta5d6ff"Ta5d6ff--beforeTa5d6ff"Tb4b4b4, Te6edf3before_argTb4b4b4,
Ta5d6ff"Ta5d6ff--afterTa5d6ff"Tb4b4b4, Te6edf3after_argTb4b4b4,
Ta5d6ff"Ta5d6ffwrite_flashTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff-zTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ff--flash_modeTa5d6ff"Tb4b4b4, Te6edf3modeTb4b4b4,
Ta5d6ff"Ta5d6ff--flash_freqTa5d6ff"Tb4b4b4, Te6edf3FLASH_FREQTb4b4b4,
Ta5d6ff"Ta5d6ff--flash_sizeTa5d6ff"Tb4b4b4, Te6edf3flash_sizeTb4b4b4,
Tb4b4b4]
Tff7b72if Te6edf3inline_verifyTb4b4b4:
Te6edf3cmdTff7b72.Td2a8ffappendTb4b4b4(Ta5d6ff"Ta5d6ff--verifyTa5d6ff"Tb4b4b4)
Te6edf3cmd Tff7b72+Tff7b72= Tb4b4b4[Te6edf3flash_addrTb4b4b4, Te6edf3firmware_pathTb4b4b4]
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffRunning: Ta5d6ff" Tff7b72+ Ta5d6ff"Ta5d6ff Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3cmdTb4b4b4[Tff7b72-T79c0ff8Tb4b4b4:Tb4b4b4]Tb4b4b4)Tb4b4b4)
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(Te6edf3cmdTb4b4b4)
Tff7b72if Te6edf3resultTff7b72.Td2a8ffreturncode Tff7b72!= T79c0ff0Tb4b4b4:
Tff7b72return Tff7b72False
Tff7b72if Te6edf3post_write_verifyTb4b4b4:
Tff7b72return Te6edf3verify_firmwareTb4b4b4(
Te6edf3firmware_pathTb4b4b4,
Te6edf3portTb4b4b4,
Te6edf3esptool_cmdTb4b4b4,
Te6edf3baudTff7b72=Te6edf3baudTb4b4b4,
Te6edf3flash_modeTff7b72=Te6edf3modeTb4b4b4,
Te6edf3no_hard_resetTff7b72=Te6edf3no_hard_resetTb4b4b4,
Tb4b4b4)
Tff7b72return Tff7b72True
Tff7b72def Td2a8ff_monitor_bootTb4b4b4(Te6edf3portTb4b4b4, Te6edf3timeoutTff7b72=T79c0ff8Tb4b4b4)Tb4b4b4:
T8b949e"""Open serial port and watch for boot errors for `timeout` seconds.
Returns:
(True, output) — device appears to be booting normally
(False, output) — bootloop detected (ets_loader.c / repeated resets)
(None, reason) — could not open serial port
"""
Tff7b72tryTb4b4b4:
Tff7b72import T7ee787serial Tff7b72as T7ee787pyserial
Tff7b72except Tf85149ImportErrorTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6ff"Ta5d6ffpyserial not installed — skipping boot checkTa5d6ff"
Tff7b72tryTb4b4b4:
Te6edf3ser Tff7b72= Te6edf3pyserialTff7b72.Td2a8ffSerialTb4b4b4(Te6edf3portTb4b4b4, T79c0ff115200Tb4b4b4, Te6edf3timeoutTff7b72=T79c0ff1Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Tff7b72NoneTb4b4b4, Ta5d6fffTa5d6ff"Ta5d6ffCould not open Tffd700{Te6edf3portTffd700}Ta5d6ff: Tffd700{Te6edf3eTffd700}Ta5d6ff"
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ff Monitoring boot on Tffd700{Te6edf3portTffd700}Ta5d6ff for Tffd700{Te6edf3timeoutTffd700}Ta5d6ffs...Ta5d6ff"Tb4b4b4)
Te6edf3output Tff7b72= Ta5d6ff"Ta5d6ff"
Te6edf3reset_count Tff7b72= T79c0ff0
Te6edf3deadline Tff7b72= Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72+ Te6edf3timeout
Tff7b72tryTb4b4b4:
Tff7b72while Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72< Te6edf3deadlineTb4b4b4:
Te6edf3raw Tff7b72= Te6edf3serTff7b72.Td2a8ffreadTb4b4b4(Te6edf3serTff7b72.Td2a8ffin_waiting Tff7b72or T79c0ff1Tb4b4b4)
Tff7b72if Te6edf3rawTb4b4b4:
Te6edf3text Tff7b72= Te6edf3rawTff7b72.Td2a8ffdecodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4, Te6edf3errorsTff7b72=Ta5d6ff"Ta5d6ffreplaceTa5d6ff"Tb4b4b4)
Te6edf3output Tff7b72+Tff7b72= Te6edf3text
T8b949e# Count ROM reset lines — 2+ means bootloop
Te6edf3reset_count Tff7b72+Tff7b72= Te6edf3textTff7b72.Td2a8ffcountTb4b4b4(Ta5d6ff"Ta5d6ffets_loader.cTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3reset_count Tff7b72>Tff7b72= T79c0ff2Tb4b4b4:
Te6edf3serTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72FalseTb4b4b4, Te6edf3output
T8b949e# Any application output means boot succeeded
Tff7b72if Ta5d6ff"Ta5d6ff[Boundary]Ta5d6ff" Tff7b72in Te6edf3output Tff7b72or Ta5d6ff"Ta5d6ffNodeTa5d6ff" Tff7b72in Te6edf3output Tff7b72or Ta5d6ff"Ta5d6ffWiFiTa5d6ff" Tff7b72in Te6edf3outputTb4b4b4:
Te6edf3serTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72TrueTb4b4b4, Te6edf3output
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
Tff7b72finallyTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3serTff7b72.Td2a8ffcloseTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72pass
T8b949e# If we got reset output but only once, device may still be trying to boot
Tff7b72if Te6edf3reset_count Tff7b72>Tff7b72= T79c0ff1 Tff7b72and Tb4b4b4(Ta5d6ff"Ta5d6ffets_loader.cTa5d6ff" Tff7b72in Te6edf3outputTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72FalseTb4b4b4, Te6edf3output
T8b949e# No clear signal — assume OK (normal if serial takes time)
Tff7b72return Tff7b72TrueTb4b4b4, Te6edf3output
T8b949e# ── Main ───────────────────────────────────────────────────────────────────────
Tff7b72def Td2a8ffmainTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72global Te6edf3_boardTb4b4b4, Te6edf3_detected_flash_size
Te6edf3parser Tff7b72= Te6edf3argparseTff7b72.Td2a8ffArgumentParserTb4b4b4(
Te6edf3descriptionTff7b72=Ta5d6ff"Ta5d6ffRTNode-HeltecV4 Flash Utility — flash transport node firmware to Heltec V3/V4Ta5d6ff"Tb4b4b4,
Te6edf3formatter_classTff7b72=Te6edf3argparseTff7b72.Td2a8ffRawDescriptionHelpFormatterTb4b4b4,
Te6edf3epilogTff7b72=Ta5d6ff"""
Ta5d6ffExamples:
Ta5d6ff python flash.py
Ta5d6ff Download latest firmware and flash default board.
Ta5d6ff python flash.py --update
Ta5d6ff Legacy alias for an app-only update.
Ta5d6ff python flash.py --use-system-esptool
Ta5d6ff Prefer a host-installed esptool over the bundled Release copy.
Ta5d6ff python flash.py --board v3
Ta5d6ff Download latest firmware and flash a V3 board.
Ta5d6ff python flash.py --release v1.0.12
Ta5d6ff Flash a specific release tag.
Ta5d6ff python flash.py --full
Ta5d6ff Do a full flash with the merged binary.
Ta5d6ff python flash.py --offline
Ta5d6ff Use only cached or local firmware.
Ta5d6ff python flash.py --file firmware.bin
Ta5d6ff Flash a specific local binary.
Ta5d6ff python flash.py --merge-only
Ta5d6ff Build the merged release binary without flashing.
Ta5d6ff python flash.py --port /dev/ttyACM0
Ta5d6ff Use a specific serial port.
Ta5d6ff python flash.py --erase
Ta5d6ff Erase flash first, then do a full flash.
Ta5d6ff Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--boardTa5d6ff"Tb4b4b4, Te6edf3choicesTff7b72=Tb4b4b4[Ta5d6ff"Ta5d6ffv3Ta5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffv4Ta5d6ff"Tb4b4b4]Tb4b4b4, Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffTarget board: v3 (Heltec V3) or v4 (Heltec V4). Ta5d6ff"
Ta5d6ff"Ta5d6ffAuto-detected from connected device if omitted.Ta5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--fileTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff-fTa5d6ff"Tb4b4b4, Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffPath to firmware binary to flashTa5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--portTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff-pTa5d6ff"Tb4b4b4, Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffSerial port (auto-detected if omitted)Ta5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--baudTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff-bTa5d6ff"Tb4b4b4, Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4, Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffBaud rate (board-specific default)Ta5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--releaseTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff-rTa5d6ff"Tb4b4b4, Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4, Te6edf3metavarTff7b72=Ta5d6ff"Ta5d6ffTAGTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffFlash a specific release version (e.g. v1.0.12)Ta5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--updateTa5d6ff"Tb4b4b4, Te6edf3actionTff7b72=Ta5d6ff"Ta5d6ffstore_trueTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffLegacy alias for an app-only firmware updateTa5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--offlineTa5d6ff"Tb4b4b4, Te6edf3actionTff7b72=Ta5d6ff"Ta5d6ffstore_trueTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffSkip online check — use cached or local firmware onlyTa5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--merge-onlyTa5d6ff"Tb4b4b4, Te6edf3actionTff7b72=Ta5d6ff"Ta5d6ffstore_trueTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffMerge PlatformIO build output into single binary, donTa5d6ff'Ta5d6fft flashTa5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--fullTa5d6ff"Tb4b4b4, Te6edf3actionTff7b72=Ta5d6ff"Ta5d6ffstore_trueTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffFlash merged binary (bootloader + partitions + app) — overwrites everythingTa5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--eraseTa5d6ff"Tb4b4b4, Te6edf3actionTff7b72=Ta5d6ff"Ta5d6ffstore_trueTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffErase entire flash before writing (implies --full)Ta5d6ff"Tb4b4b4)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--use-system-esptoolTa5d6ff"Tb4b4b4, Te6edf3actionTff7b72=Ta5d6ff"Ta5d6ffstore_trueTa5d6ff"Tb4b4b4,
Te6edf3helpTff7b72=Ta5d6ff"Ta5d6ffUse a host-installed esptool instead of the bundled Release copyTa5d6ff"Tb4b4b4)
T8b949e# Power-user override (not shown in --help)
Te6edf3parserTff7b72.Td2a8ffadd_argumentTb4b4b4(Ta5d6ff"Ta5d6ff--flash-modeTa5d6ff"Tb4b4b4, Te6edf3defaultTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3helpTff7b72=Te6edf3argparseTff7b72.Td2a8ffSUPPRESSTb4b4b4)
Te6edf3args Tff7b72= Te6edf3parserTff7b72.Td2a8ffparse_argsTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3argsTff7b72.Td2a8ffupdate Tff7b72and Te6edf3argsTff7b72.Td2a8ffofflineTb4b4b4:
Te6edf3parserTff7b72.Td2a8fferrorTb4b4b4(Ta5d6ff"Ta5d6ff--update cannot be combined with --offlineTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3argsTff7b72.Td2a8ffupdateTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffUsing legacy compatibility flag; default behavior already downloads and flashes the latest firmware unless --offline is set.Ta5d6ff"Tb4b4b4)
T8b949e# Find esptool early — needed for both auto-detect and flashing
Te6edf3esptool_cmd Tff7b72= Te6edf3find_esptoolTb4b4b4(Te6edf3prefer_systemTff7b72=Te6edf3argsTff7b72.Td2a8ffuse_system_esptoolTb4b4b4)
Tff7b72if Tff7b72not Te6edf3esptool_cmdTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffError: esptool not found!Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffExpected one of:Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff 1. Bundled standalone binary: Release/esptool/esptoolTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff 2. PlatformIOTa5d6ff'Ta5d6ffs packaged esptool (with pyserial installed)Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff 3. A host-installed esptool (pip install esptool)Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
T8b949e# ── Board detection ─────────────────────────────────────────────────
Te6edf3detected_info Tff7b72= Tff7b72None
Te6edf3_early_port Tff7b72= Tff7b72None
Tff7b72if Te6edf3argsTff7b72.Td2a8ffboardTb4b4b4:
T8b949e# Explicit board — keep the selected profile, but still probe the device
T8b949e# when a port is available so flash size can override stale profile data.
Te6edf3_board Tff7b72= Te6edf3argsTff7b72.Td2a8ffboard
Te6edf3_early_port Tff7b72= Te6edf3argsTff7b72.Td2a8ffport Tff7b72or Te6edf3find_serial_portTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3_early_portTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffReading flash info from Tffd700{Te6edf3_early_portTffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
Te6edf3infoTb4b4b4, Te6edf3err Tff7b72= Te6edf3read_flash_infoTb4b4b4(Te6edf3_early_portTb4b4b4, Te6edf3esptool_cmdTb4b4b4)
Tff7b72if Te6edf3infoTb4b4b4:
Te6edf3detected_info Tff7b72= Te6edf3info
Te6edf3actual_flash Tff7b72= Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffflash_sizeTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3actual_flashTb4b4b4:
Te6edf3_detected_flash_size Tff7b72= Te6edf3actual_flash
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Chip: Tffd700{Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6ffchipTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Flash: Tffd700{Te6edf3actual_flash Tff7b72or Ta5d6ff'Ta5d6ff?Ta5d6ff'Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Features: Tffd700{Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fffeaturesTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff MAC: Tffd700{Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6ffmacTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Flash probe failed: Tffd700{Te6edf3errTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3argsTff7b72.Td2a8ffmerge_onlyTb4b4b4:
T8b949e# No device needed for merge — fall back to default
Te6edf3_board Tff7b72= Te6edf3DEFAULT_BOARD
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff(No --board specified; defaulting to Tffd700{Te6edf3DEFAULT_BOARDTffd700}Ta5d6ff for merge)Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
T8b949e# Auto-detect from connected device
Te6edf3_early_port Tff7b72= Te6edf3argsTff7b72.Td2a8ffport Tff7b72or Te6edf3find_serial_portTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3_early_portTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffNo serial port detected and no --board specified.Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffDefaulting to Tffd700{Te6edf3DEFAULT_BOARDTffd700}Ta5d6ff. Specify with --board v3 or --board v4.Ta5d6ff"Tb4b4b4)
Te6edf3_board Tff7b72= Te6edf3DEFAULT_BOARD
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffDetecting board on Tffd700{Te6edf3_early_portTffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
Te6edf3board_keyTb4b4b4, Te6edf3info Tff7b72= Te6edf3detect_boardTb4b4b4(Te6edf3_early_portTb4b4b4, Te6edf3esptool_cmdTb4b4b4)
Tff7b72if Te6edf3board_keyTb4b4b4:
Te6edf3_board Tff7b72= Te6edf3board_key
Te6edf3detected_info Tff7b72= Te6edf3info
Te6edf3actual_flash Tff7b72= Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffflash_sizeTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3actual_flashTb4b4b4:
Te6edf3_detected_flash_size Tff7b72= Te6edf3actual_flash
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Chip: Tffd700{Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6ffchipTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Flash: Tffd700{Te6edf3actual_flash Tff7b72or Ta5d6ff'Ta5d6ff?Ta5d6ff'Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Features: Tffd700{Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fffeaturesTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff MAC: Tffd700{Te6edf3infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6ffmacTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff → Detected: Tffd700{Te6edf3BOARD_PROFILESTb4b4b4[Te6edf3board_keyTb4b4b4]Tb4b4b4[Ta5d6ff'Ta5d6ffnameTa5d6ff'Tb4b4b4]Tffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3reason Tff7b72= Te6edf3info T8b949e# info is the error reason when board_key is None
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Auto-detect failed: Tffd700{Te6edf3reasonTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Defaulting to Tffd700{Te6edf3DEFAULT_BOARDTffd700}Ta5d6ff. Specify with --board v3 or --board v4.Ta5d6ff"Tb4b4b4)
Te6edf3_board Tff7b72= Te6edf3DEFAULT_BOARD
Te6edf3baud Tff7b72= Te6edf3argsTff7b72.Td2a8ffbaud Tff7b72or Te6edf3BAUD_RATETb4b4b4(Tb4b4b4)
Te6edf3bp Tff7b72= Te6edf3board_profileTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff╔══════════════════════════════════════════╗Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff║ RTNode-HeltecV4 Flash Utility ║Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff║ Tffd700{Te6edf3bpTb4b4b4[Ta5d6ff'Ta5d6ffnameTa5d6ff'Tb4b4b4]Tffd700:Ta5d6ff^40sTffd700}Ta5d6ff ║Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff╚══════════════════════════════════════════╝Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing esptool: Tffd700{Ta5d6ff'Ta5d6ff Ta5d6ff'Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3esptool_cmdTb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
T8b949e# --erase implies --full (after erase, device needs bootloader + partitions)
Tff7b72if Te6edf3argsTff7b72.Td2a8fferaseTb4b4b4:
Te6edf3argsTff7b72.Td2a8fffull Tff7b72= Tff7b72True
T8b949e# Apply flash mode override (hidden --flash-mode flag for power users)
Tff7b72global Te6edf3_flash_mode_override
Tff7b72if Te6edf3argsTff7b72.Td2a8ffflash_modeTb4b4b4:
Te6edf3_flash_mode_override Tff7b72= Te6edf3argsTff7b72.Td2a8ffflash_mode
Te6edf3fv Tff7b72= Te6edf3flash_variantTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Flash size: Tffd700{Te6edf3FLASH_SIZETb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"
Tff7b72+ Tb4b4b4(Ta5d6ff"Ta5d6ff (detected from device)Ta5d6ff" Tff7b72if Te6edf3_detected_flash_size Tff7b72else Ta5d6ff"Ta5d6ff (conservative default)Ta5d6ff"Tb4b4b4)Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Variant: Tffd700{Te6edf3fvTb4b4b4[Ta5d6ff'Ta5d6fffirmware_binTa5d6ff'Tb4b4b4]Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Flash mode: Tffd700{Te6edf3BOARD_FLASH_MODETb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"
Tff7b72+ Tb4b4b4(Ta5d6ff"Ta5d6ff (override)Ta5d6ff" Tff7b72if Te6edf3_flash_mode_override Tff7b72else Ta5d6ff"Ta5d6ff (board default)Ta5d6ff"Tb4b4b4)Tb4b4b4)
T8b949e# Determine firmware file
Te6edf3firmware_path Tff7b72= Tff7b72None
T8b949e# Paths within the PlatformIO build output dir
Te6edf3merged_fn Tff7b72= Te6edf3MERGED_BINTb4b4b4(Tb4b4b4) Tff7b72or Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffjoinTb4b4b4(Te6edf3fvTb4b4b4[Ta5d6ff"Ta5d6ffbuild_dirTa5d6ff"Tb4b4b4]Tb4b4b4,
Te6edf3fvTb4b4b4[Ta5d6ff"Ta5d6fffirmware_binTa5d6ff"Tb4b4b4]Tff7b72.Td2a8ffreplaceTb4b4b4(Ta5d6ff"Ta5d6ff.binTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff_merged.binTa5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3firmware_bin Tff7b72= Te6edf3FIRMWARE_BINTb4b4b4(Tb4b4b4)
Te6edf3pio_env Tff7b72= Te6edf3PIO_ENVTb4b4b4(Tb4b4b4)
T8b949e# Merged binary name shown in the variant (used for cache lookup)
Te6edf3merged_bin_name Tff7b72= Te6edf3fvTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffmerged_binTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3argsTff7b72.Td2a8fffileTb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3argsTff7b72.Td2a8fffile
Tff7b72if Tff7b72not Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3firmware_pathTb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffError: file not found: Tffd700{Te6edf3firmware_pathTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tff7b72elif Te6edf3argsTff7b72.Td2a8ffmerge_onlyTb4b4b4:
Tff7b72if Te6edf3merge_firmwareTb4b4b4(Te6edf3merged_fnTb4b4b4, Te6edf3esptool_cmdTb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ffDone! Flash with: python flash.py --board Tffd700{Te6edf3_boardTffd700}Ta5d6ff --file Tffd700{Te6edf3merged_fnTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tff7b72return
Tff7b72elif Te6edf3argsTff7b72.Td2a8fffull Tff7b72and Tff7b72not Te6edf3argsTff7b72.Td2a8ffrelease Tff7b72and Te6edf3argsTff7b72.Td2a8ffofflineTb4b4b4:
T8b949e# Full flash, offline: prefer release-ZIP firmware next to flash.py,
T8b949e# then PIO build output, then firmware cache.
Te6edf3local Tff7b72= Te6edf3find_local_firmwareTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3localTb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3local
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing local firmware: Tffd700{Te6edf3localTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3firmware_binTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3merged_fnTb4b4b4)Tb4b4b4:
Te6edf3build_time Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetmtimeTb4b4b4(Te6edf3firmware_binTb4b4b4)
Te6edf3merge_time Tff7b72= Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetmtimeTb4b4b4(Te6edf3merged_fnTb4b4b4)
Tff7b72if Te6edf3build_time Tff7b72> Te6edf3merge_timeTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffBuild output is newer than merged binary, re-merging...Ta5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3merge_firmwareTb4b4b4(Te6edf3merged_fnTb4b4b4, Te6edf3esptool_cmdTb4b4b4)Tb4b4b4:
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffCreating merged binary from PlatformIO build output...Ta5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3merge_firmwareTb4b4b4(Te6edf3merged_fnTb4b4b4, Te6edf3esptool_cmdTb4b4b4)Tb4b4b4:
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Te6edf3firmware_path Tff7b72= Te6edf3merged_fn
Tff7b72elif Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3merged_fnTb4b4b4)Tb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3merged_fn
Tff7b72elseTb4b4b4:
T8b949e# Try firmware cache (previously downloaded)
Te6edf3cached_merged Tff7b72= Te6edf3_extracted_firmware_pathTb4b4b4(Te6edf3merged_bin_nameTb4b4b4) Tff7b72if Te6edf3merged_bin_name Tff7b72else Tff7b72None
Te6edf3cached_app Tff7b72= Te6edf3_extracted_firmware_pathTb4b4b4(Te6edf3fvTb4b4b4[Ta5d6ff"Ta5d6fffirmware_binTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3cached_merged Tff7b72and Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3cached_mergedTb4b4b4)Tb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3cached_merged
Te6edf3meta Tff7b72= Te6edf3_read_cache_metaTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing cached firmware: Tffd700{Te6edf3metaTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fftagTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4) Tff7b72if Te6edf3meta Tff7b72else Ta5d6ff'Ta5d6ff?Ta5d6ff'Tffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3cached_appTb4b4b4)Tb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3cached_app
Te6edf3meta Tff7b72= Te6edf3_read_cache_metaTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing cached firmware: Tffd700{Te6edf3metaTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fftagTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4) Tff7b72if Te6edf3meta Tff7b72else Ta5d6ff'Ta5d6ff?Ta5d6ff'Tffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffNo firmware found for full flash!Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffOptions:Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff 1. Use firmware from the release ZIP (extract next to flash.py)Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff 2. Build with PlatformIO: pio run -e Tffd700{Te6edf3pio_envTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff 3. Run without --offline to download from GitHubTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff 4. Specify a file: python flash.py --board Tffd700{Te6edf3_boardTffd700}Ta5d6ff --file <path>Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tff7b72elseTb4b4b4:
T8b949e# Default path: fetch from GitHub (unless --offline)
Tff7b72if Tff7b72not Te6edf3argsTff7b72.Td2a8ffofflineTb4b4b4:
Te6edf3fw_pathTb4b4b4, Te6edf3tag_or_err Tff7b72= Te6edf3fetch_firmwareTb4b4b4(Te6edf3_boardTb4b4b4, Te6edf3FLASH_SIZETb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3release_tagTff7b72=Te6edf3argsTff7b72.Td2a8ffreleaseTb4b4b4)
Tff7b72if Te6edf3fw_pathTb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3fw_path
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ff Release: Tffd700{Te6edf3tag_or_errTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ff GitHub: Tffd700{Te6edf3tag_or_errTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Falling back to local firmware...Ta5d6ff"Tb4b4b4)
T8b949e# Fall back to local firmware alongside flash.py, PIO build, or cache
Tff7b72if Tff7b72not Te6edf3firmware_pathTb4b4b4:
Te6edf3local Tff7b72= Te6edf3find_local_firmwareTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3localTb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3local
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing local firmware: Tffd700{Te6edf3localTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3firmware_binTb4b4b4)Tb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3firmware_bin
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing local PlatformIO build: Tffd700{Te6edf3firmware_binTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3cached_merged Tff7b72= Te6edf3_extracted_firmware_pathTb4b4b4(Te6edf3merged_bin_nameTb4b4b4) Tff7b72if Te6edf3merged_bin_name Tff7b72else Tff7b72None
Te6edf3cached_app Tff7b72= Te6edf3_extracted_firmware_pathTb4b4b4(Te6edf3fvTb4b4b4[Ta5d6ff"Ta5d6fffirmware_binTa5d6ff"Tb4b4b4]Tb4b4b4)
Tff7b72if Te6edf3cached_merged Tff7b72and Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3cached_mergedTb4b4b4)Tb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3cached_merged
Te6edf3meta Tff7b72= Te6edf3_read_cache_metaTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing cached firmware: Tffd700{Te6edf3metaTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fftagTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4) Tff7b72if Te6edf3meta Tff7b72else Ta5d6ff'Ta5d6ff?Ta5d6ff'Tffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3cached_appTb4b4b4)Tb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3cached_app
Te6edf3meta Tff7b72= Te6edf3_read_cache_metaTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing cached firmware: Tffd700{Te6edf3metaTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff'Ta5d6fftagTa5d6ff'Tb4b4b4, Ta5d6ff'Ta5d6ff?Ta5d6ff'Tb4b4b4) Tff7b72if Te6edf3meta Tff7b72else Ta5d6ff'Ta5d6ff?Ta5d6ff'Tffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffisfileTb4b4b4(Te6edf3merged_fnTb4b4b4)Tb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3merged_fn
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing local merged binary: Tffd700{Te6edf3merged_fnTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffNo firmware found!Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffOptions:Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff 1. Use firmware from the release ZIP (extract next to flash.py)Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff 2. Build with PlatformIO: pio run -e Tffd700{Te6edf3pio_envTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff 3. Specify a file: python flash.py --board Tffd700{Te6edf3_boardTffd700}Ta5d6ff --file <path>Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
T8b949e# ── Device checks & flash decision ──────────────────────────────────────
T8b949e#
T8b949e# Flow:
T8b949e# 1. --full or --erase on CLI → full_flash = True
T8b949e# 2. Check if app firmware exists on device → if not → full_flash = True
T8b949e# 3. Check partition table matches expected → if not → full_flash = True
T8b949e# 4. Ask user "Erase flash before writing?" → Y → full_flash = True
T8b949e# 5. full_flash → flash merged binary at 0x0000
T8b949e# 6. Otherwise → extract app from merged, flash at 0x10000
T8b949e# Reuse early-detected port, or find one now
Te6edf3port Tff7b72= Te6edf3argsTff7b72.Td2a8ffport Tff7b72or Te6edf3_early_port Tff7b72or Te6edf3find_serial_portTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3portTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffError: No serial port detected!Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffConnect your Tffd700{Te6edf3bpTb4b4b4[Ta5d6ff'Ta5d6ffnameTa5d6ff'Tb4b4b4]Tffd700}Ta5d6ff via USB and try again,Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffor specify manually: python flash.py --board Tffd700{Te6edf3_boardTffd700}Ta5d6ff --port /dev/ttyACM0Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ffSerial port: Tffd700{Te6edf3portTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffFirmware: Tffd700{Te6edf3firmware_pathTffd700}Ta5d6ff (Tffd700{Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3firmware_pathTb4b4b4)Tffd700:Ta5d6ff,Tffd700}Ta5d6ff bytes)Ta5d6ff"Tb4b4b4)
Te6edf3full_flash Tff7b72= Te6edf3argsTff7b72.Td2a8fffull Tff7b72or Te6edf3argsTff7b72.Td2a8fferase
Tff7b72if Tff7b72not Te6edf3full_flashTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffChecking device state...Ta5d6ff"Tb4b4b4)
Te6edf3has_app Tff7b72= Te6edf3check_app_on_deviceTb4b4b4(Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTb4b4b4)
Tff7b72if Tff7b72not Te6edf3has_appTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff No app firmware on device — full flash requiredTa5d6ff"Tb4b4b4)
Te6edf3full_flash Tff7b72= Tff7b72True
Tff7b72if Tff7b72not Te6edf3full_flashTb4b4b4:
Te6edf3pt_ok Tff7b72= Te6edf3check_partition_tableTb4b4b4(Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTb4b4b4)
Tff7b72if Tff7b72not Te6edf3pt_okTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Partition table mismatch — full flash requiredTa5d6ff"Tb4b4b4)
Te6edf3full_flash Tff7b72= Tff7b72True
Tff7b72if Tff7b72not Te6edf3full_flashTb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3erase_choice Tff7b72= Tffa657inputTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffErase flash before writing? (wipes all settings) [y/N] Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149EOFErrorTb4b4b4:
Te6edf3erase_choice Tff7b72= Ta5d6ff"Ta5d6ff"
Tff7b72if Te6edf3erase_choice Tff7b72== Ta5d6ff"Ta5d6ffyTa5d6ff"Tb4b4b4:
Te6edf3full_flash Tff7b72= Tff7b72True
T8b949e# ── Prepare firmware based on flash decision ────────────────────────────
Tff7b72if Te6edf3full_flashTb4b4b4:
T8b949e# Need the merged binary — ensure we have one
Tff7b72if Tff7b72not Te6edf3is_merged_binaryTb4b4b4(Te6edf3firmware_pathTb4b4b4)Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffCreating merged binary for full flash...Ta5d6ff"Tb4b4b4)
Te6edf3merged Tff7b72= Te6edf3auto_merge_app_binaryTb4b4b4(Te6edf3firmware_pathTb4b4b4, Te6edf3esptool_cmdTb4b4b4)
Tff7b72if Te6edf3mergedTb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3merged
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff ERROR: Cannot create merged binary — missing boot components.Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Build with PlatformIO first: pio run -e Tffd700{Te6edf3PIO_ENVTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ff Full flash: Tffd700{Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Te6edf3firmware_pathTb4b4b4)Tffd700}Ta5d6ff → 0xTffd700{Te6edf3BOOTLOADER_ADDRTffd700:Ta5d6ff04xTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Size: Tffd700{Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3firmware_pathTb4b4b4)Tffd700:Ta5d6ff,Tffd700}Ta5d6ff bytesTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff ⚠ This will overwrite all settings (NVS/EEPROM)Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
T8b949e# Extract app-only from merged binary to preserve settings
Tff7b72if Te6edf3is_merged_binaryTb4b4b4(Te6edf3firmware_pathTb4b4b4)Tb4b4b4:
Te6edf3app_path Tff7b72= Te6edf3extract_app_from_mergedTb4b4b4(Te6edf3firmware_pathTb4b4b4)
Tff7b72if Te6edf3app_pathTb4b4b4:
Te6edf3firmware_path Tff7b72= Te6edf3app_path
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ff ERROR: Could not extract app from merged binary.Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ff App-only update: Tffd700{Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffbasenameTb4b4b4(Te6edf3firmware_pathTb4b4b4)Tffd700}Ta5d6ff → 0xTffd700{Te6edf3APP_ADDRTffd700:Ta5d6ff05xTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Size: Tffd700{Te6edf3osTff7b72.Td2a8ffpathTff7b72.Td2a8ffgetsizeTb4b4b4(Te6edf3firmware_pathTb4b4b4)Tffd700:Ta5d6ff,Tffd700}Ta5d6ff bytesTa5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff WiFi/transport settings will be preservedTa5d6ff"Tb4b4b4)
T8b949e# ── Interactive options ─────────────────────────────────────────────────
T8b949e# Offer 1200 baud reset if device might be stuck
Tff7b72tryTb4b4b4:
Te6edf3reset_choice Tff7b72= Tffa657inputTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffReset device to download mode first? (try if device is stuck) [y/N] Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149EOFErrorTb4b4b4:
Te6edf3reset_choice Tff7b72= Ta5d6ff"Ta5d6ff"
Tff7b72if Te6edf3reset_choice Tff7b72== Ta5d6ff"Ta5d6ffyTa5d6ff"Tb4b4b4:
Te6edf3reset_to_bootloaderTb4b4b4(Te6edf3portTb4b4b4)
T8b949e# Port may change after reset — re-scan
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffRe-scanning serial ports (port may have changed)...Ta5d6ff"Tb4b4b4)
Te6edf3new_port Tff7b72= Te6edf3argsTff7b72.Td2a8ffport Tff7b72or Te6edf3find_serial_portTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3new_portTb4b4b4:
Te6edf3port Tff7b72= Te6edf3new_port
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffUsing port: Tffd700{Te6edf3portTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ffWarning: No ports found after reset. Continuing with Tffd700{Te6edf3portTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3confirm Tff7b72= Tffa657inputTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffFlash firmware? [Y/n] Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3confirm Tff7b72and Te6edf3confirm Tff7b72!= Ta5d6ff"Ta5d6ffyTa5d6ff"Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffAborted.Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff0Tb4b4b4)
T8b949e# ── Erase flash (only when --erase was explicitly passed) ───────────────
Te6edf3erase_performed Tff7b72= Tff7b72False
Tff7b72if Te6edf3argsTff7b72.Td2a8fferaseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ffErasing flash on Tffd700{Te6edf3portTffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
T8b949e# Use --after no_reset so the device stays in the esptool stub after
T8b949e# erasing. This avoids exiting download mode (which would require
T8b949e# DTR/RTS re-entry and can fail on some USB-UART bridges).
Te6edf3erase_cmd Tff7b72= Te6edf3esptool_cmd Tff7b72+ Tb4b4b4[
Ta5d6ff"Ta5d6ff--chipTa5d6ff"Tb4b4b4, Te6edf3CHIPTb4b4b4,
Ta5d6ff"Ta5d6ff--portTa5d6ff"Tb4b4b4, Te6edf3portTb4b4b4,
Ta5d6ff"Ta5d6ff--baudTa5d6ff"Tb4b4b4, Te6edf3baudTb4b4b4,
Ta5d6ff"Ta5d6ff--afterTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffno_resetTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fferase_flashTa5d6ff"Tb4b4b4,
Tb4b4b4]
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(Te6edf3erase_cmdTb4b4b4)
Tff7b72if Te6edf3resultTff7b72.Td2a8ffreturncode Tff7b72!= T79c0ff0Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffErase FAILED.Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Te6edf3erase_performed Tff7b72= Tff7b72True
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffFlash erased (device still in download mode).Ta5d6ff"Tb4b4b4)
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff1Tb4b4b4) T8b949e# brief settle
T8b949e# ── Flash + auto-verify + boot-check + auto-retry ───────────────────────
T8b949e#
T8b949e# Strategy:
T8b949e# 1. Flash with the board's default flash mode
T8b949e# 2. If this is a full flash (any path), always add --verify
T8b949e# 3. After successful flash+verify, monitor serial for bootloop
T8b949e# 4. If bootloop detected and current mode != DIO, auto-retry with DIO
T8b949e#
Te6edf3current_mode Tff7b72= Te6edf3BOARD_FLASH_MODETb4b4b4(Tb4b4b4)
Te6edf3ok Tff7b72= Te6edf3flash_firmwareTb4b4b4(Te6edf3firmware_pathTb4b4b4, Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTb4b4b4,
Te6edf3no_reset_beforeTff7b72=Te6edf3erase_performedTb4b4b4,
Te6edf3verifyTff7b72=Te6edf3full_flashTb4b4b4)
Tff7b72if Tff7b72not Te6edf3okTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ffFlash FAILED. Check connection and try again.Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ffYou may need to hold BOOT while pressing RESET.Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
T8b949e# ── Post-flash boot monitoring (on any full flash) ──────────────────────
Tff7b72if Te6edf3full_flashTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ff Verifying device boots correctly...Ta5d6ff"Tb4b4b4)
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff2Tb4b4b4) T8b949e# Give device time to start booting
Te6edf3boot_okTb4b4b4, Te6edf3boot_output Tff7b72= Te6edf3_monitor_bootTb4b4b4(Te6edf3portTb4b4b4, Te6edf3timeoutTff7b72=T79c0ff8Tb4b4b4)
Tff7b72if Te6edf3boot_ok Tff7b72is Tff7b72NoneTb4b4b4:
T8b949e# Couldn't open serial — not fatal, just warn
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff ⚠ Tffd700{Te6edf3boot_outputTffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Cannot verify boot — check device manuallyTa5d6ff"Tb4b4b4)
Tff7b72elif Te6edf3boot_okTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff ✓ Device is booting normallyTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
T8b949e# Bootloop detected!
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ff ✗ BOOTLOOP DETECTED — device is not booting properlyTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3boot_outputTb4b4b4:
T8b949e# Show the first few relevant lines
Tff7b72for Te6edf3line Tff7b72in Te6edf3boot_outputTff7b72.Td2a8ffsplitlinesTb4b4b4(Tb4b4b4)Tb4b4b4[Tb4b4b4:T79c0ff8Tb4b4b4]Tb4b4b4:
Te6edf3line Tff7b72= Te6edf3lineTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3lineTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff Tffd700{Te6edf3lineTffd700}Ta5d6ff"Tb4b4b4)
Tff7b72if Te6edf3current_mode Tff7b72!= Ta5d6ff"Ta5d6ffdioTa5d6ff"Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ff Current flash mode is Tffd700{Te6edf3current_modeTff7b72.Td2a8ffupperTb4b4b4(Tb4b4b4)Tffd700}Ta5d6ff — retrying with DIO...Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff (DIO is more compatible with all flash chip variants)Ta5d6ff"Tb4b4b4)
T8b949e# Need to re-enter download mode: reset via 1200 baud
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Resetting device to download mode...Ta5d6ff"Tb4b4b4)
Te6edf3reset_to_bootloaderTb4b4b4(Te6edf3portTb4b4b4)
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff3Tb4b4b4)
Te6edf3new_port Tff7b72= Te6edf3argsTff7b72.Td2a8ffport Tff7b72or Te6edf3find_serial_portTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3new_portTb4b4b4:
Te6edf3port Tff7b72= Te6edf3new_port
T8b949e# Re-erase if we erased before (flash is garbage after bootloop)
Tff7b72if Te6edf3argsTff7b72.Td2a8fferaseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Tffea00\nTa5d6ff Re-erasing flash on Tffd700{Te6edf3portTffd700}Ta5d6ff...Ta5d6ff"Tb4b4b4)
Te6edf3erase_cmd Tff7b72= Te6edf3esptool_cmd Tff7b72+ Tb4b4b4[
Ta5d6ff"Ta5d6ff--chipTa5d6ff"Tb4b4b4, Te6edf3CHIPTb4b4b4,
Ta5d6ff"Ta5d6ff--portTa5d6ff"Tb4b4b4, Te6edf3portTb4b4b4,
Ta5d6ff"Ta5d6ff--baudTa5d6ff"Tb4b4b4, Te6edf3baudTb4b4b4,
Ta5d6ff"Ta5d6ff--afterTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffno_resetTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fferase_flashTa5d6ff"Tb4b4b4,
Tb4b4b4]
Te6edf3result Tff7b72= Te6edf3subprocessTff7b72.Td2a8ffrunTb4b4b4(Te6edf3erase_cmdTb4b4b4)
Tff7b72if Te6edf3resultTff7b72.Td2a8ffreturncode Tff7b72!= T79c0ff0Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ff Re-erase FAILED.Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Te6edf3erase_performed Tff7b72= Tff7b72True
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff1Tb4b4b4)
Te6edf3ok Tff7b72= Te6edf3flash_firmwareTb4b4b4(Te6edf3firmware_pathTb4b4b4, Te6edf3portTb4b4b4, Te6edf3esptool_cmdTb4b4b4, Te6edf3baudTb4b4b4,
Te6edf3no_reset_beforeTff7b72=Te6edf3erase_performedTb4b4b4,
Te6edf3verifyTff7b72=Tff7b72TrueTb4b4b4, Te6edf3flash_modeTff7b72=Ta5d6ff"Ta5d6ffdioTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3okTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ff DIO retry FAILED.Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
T8b949e# Check boot again
Te6edf3timeTff7b72.Td2a8ffsleepTb4b4b4(T79c0ff2Tb4b4b4)
Te6edf3boot_ok2Tb4b4b4, Te6edf3boot_output2 Tff7b72= Te6edf3_monitor_bootTb4b4b4(Te6edf3portTb4b4b4, Te6edf3timeoutTff7b72=T79c0ff8Tb4b4b4)
Tff7b72if Te6edf3boot_ok2 Tff7b72is Tff7b72FalseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ff ✗ Still bootlooping after DIO retry.Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff This may be a hardware issue. Check connections and try a different USB cable.Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tff7b72elif Te6edf3boot_ok2Tb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff ✓ Device is booting normally with DIO mode!Ta5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff ⚠ Tffd700{Te6edf3boot_output2Tffd700}Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Could not verify boot — check device manuallyTa5d6ff"Tb4b4b4)
Tff7b72elseTb4b4b4:
Tffa657printTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ff Already using DIO mode — this may be a hardware issue.Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff Try: different USB cable, different port, or reflash the original firmware:Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6fffTa5d6ff"Ta5d6ff python flash.py --erase --board Tffd700{Te6edf3_boardTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3sysTff7b72.Td2a8ffexitTb4b4b4(T79c0ff1Tb4b4b4)
Tffa657printTb4b4b4(Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff╔══════════════════════════════════════════╗Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff║ Flash complete! ║Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff║ Device will reboot automatically. ║Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff║ ║Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff║ On first boot, hold PRG > 5s to enter ║Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff║ the configuration portal. ║Ta5d6ff"Tb4b4b4)
Tffa657printTb4b4b4(Ta5d6ff"Ta5d6ff╚══════════════════════════════════════════╝Ta5d6ff"Tb4b4b4)
Tff7b72if Tff7b72__name__ Tff7b72== Ta5d6ff"Ta5d6ff__main__Ta5d6ff"Tb4b4b4:
Te6edf3mainTb4b4b4(Tb4b4b4)
Served by rngit 1.4.2 - Generated in 0.18s